diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx index 18291f2fda79bda37109f38ba6ab8faf11c1cb47..d320b02d4411b0b532157cc29e2acec8afa2f475 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem.jsx @@ -1,11 +1,11 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { Link, withRouter } from "react-router"; +import { t } from "ttag"; import InputBlurChange from "metabase/components/InputBlurChange"; import Select, { Option } from "metabase/components/Select"; -import Icon from "metabase/components/Icon"; -import { t } from "ttag"; +import Button from "metabase/components/Button"; import * as MetabaseCore from "metabase/lib/core"; import { isNumericBaseType, isCurrency } from "metabase/lib/schema_metadata"; import { TYPE, isa, isFK } from "metabase/lib/types"; @@ -24,6 +24,7 @@ export default class Column extends Component { field: PropTypes.object, idfields: PropTypes.array.isRequired, updateField: PropTypes.func.isRequired, + dragHandle: PropTypes.node, }; updateField = properties => { @@ -44,10 +45,10 @@ export default class Column extends Component { }; render() { - const { field, idfields } = this.props; + const { field, idfields, dragHandle } = this.props; return ( - <li className="mt1 mb3 flex"> + <div className="p1 mt1 mb3 flex bordered rounded"> <div className="flex flex-column flex-auto"> <div> <InputBlurChange @@ -74,6 +75,12 @@ export default class Column extends Component { idfields={idfields} /> </div> + <Link + to={`${this.props.location.pathname}/${this.props.field.id}`} + className="text-brand-hover mr1" + > + <Button icon="gear" style={{ padding: 10 }} /> + </Link> </div> </div> </div> @@ -87,13 +94,8 @@ export default class Column extends Component { /> </div> </div> - <Link - to={`${this.props.location.pathname}/${this.props.field.id}`} - className="text-brand-hover mx2 mt1" - > - <Icon name="gear" /> - </Link> - </li> + {dragHandle} + </div> ); } } diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx index 4c10806f7ec60b688a233a76c6ad8311e0cf4aed..97aec39a8c89f1e319eb28640cda38db3e50bf9f 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnsList.jsx @@ -1,41 +1,189 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { t } from "ttag"; +import _ from "underscore"; +import { + SortableContainer, + SortableElement, + SortableHandle, +} from "react-sortable-hoc"; + +import AccordionList from "metabase/components/AccordionList"; +import PopoverWithTrigger from "metabase/components/PopoverWithTrigger"; +import Icon from "metabase/components/Icon"; +import Grabber from "metabase/components/Grabber"; + import ColumnItem from "./ColumnItem"; export default class ColumnsList extends Component { + constructor(props) { + super(props); + this.state = { fieldOrder: undefined }; + } + static propTypes = { - fields: PropTypes.array, + table: PropTypes.object, idfields: PropTypes.array, updateField: PropTypes.func.isRequired, }; + componentDidMount() { + this.setState({ fieldOrder: this.getFieldOrder(this.props.table) }); + } + + componentDidUpdate(prevProps) { + const prevFieldOrder = this.getFieldOrder(prevProps.table); + const fieldOrder = this.getFieldOrder(this.props.table); + if (!_.isEqual(fieldOrder, prevFieldOrder)) { + this.setState({ fieldOrder }); + } + } + + getFieldOrder(table) { + const { fields } = table || {}; + if (!fields) { + return; + } + const positionById = {}; + if (fields.every(field => field.position === 0)) { + // Tables sometimes come down with all field positions set to zero. + // In that case, we assume the current field order. + fields.forEach(({ id }, index) => { + positionById[id] = index; + }); + } else { + for (const { id, position } of fields) { + positionById[id] = position; + } + } + return positionById; + } + + handleSortEnd = async ({ oldIndex, newIndex }) => { + if (oldIndex === newIndex) { + return; + } + const fieldIdPositionPairs = Object.entries(this.state.fieldOrder); + const sortedFieldIds = new Array(fieldIdPositionPairs.length); + const fieldOrder = {}; + for (const [id, index] of fieldIdPositionPairs) { + const idx = + newIndex <= index && index < oldIndex + ? index + 1 // shift down + : oldIndex < index && index <= newIndex + ? index - 1 // shift up + : index === oldIndex + ? newIndex // move dragged column to new location + : index; // otherwise, leave it where it is + fieldOrder[id] = idx; + sortedFieldIds[idx] = parseInt(id); + } + this.setState({ fieldOrder }); + + await this.props.table.setFieldOrder(sortedFieldIds); + if (this.props.table.field_order !== "custom") { + await this.props.table.update({ field_order: "custom" }); + } + }; + render() { - const { fields = [] } = this.props; + const { table = {} } = this.props; + const { fields = [] } = table; + const { fieldOrder } = this.state; return ( <div id="ColumnsList" className="my3"> - <h2 className="px1 text-orange">{t`Columns`}</h2> + <div className="flex align-baseline justify-between"> + <h2 className="px1 text-orange">{t`Columns`}</h2> + <ColumnOrderDropdown table={table} /> + </div> <div className="text-uppercase text-medium py1"> <div style={{ minWidth: 420 }} className="float-left px1" >{t`Column`}</div> - <div className="flex clearfix"> - <div className="flex-half px1">{t`Visibility`}</div> - <div className="flex-half px1">{t`Type`}</div> + <div className="flex clearfix" style={{ paddingRight: 47 }}> + <div className="flex-half pl2">{t`Visibility`}</div> + <div className="flex-half">{t`Type`}</div> </div> </div> - <ol className="border-top border-bottom"> - {fields.map(field => ( - <ColumnItem + <SortableColumns + onSortEnd={this.handleSortEnd} + helperClass="ColumnSortHelper" + useDragHandle={true} + > + {(fieldOrder == null + ? fields + : _.sortBy(fields, ({ id }) => fieldOrder[id]) + ).map((field, index) => ( + <SortableColumnItem key={field.id} field={field} updateField={this.props.updateField} idfields={this.props.idfields} + dragHandle={<DragHandle />} + index={index} /> ))} - </ol> + </SortableColumns> </div> ); } } + +function Columns({ children, ...props }) { + return <div {...props}>{children}</div>; +} + +const SortableColumns = SortableContainer(Columns); + +const SortableColumnItem = SortableElement(ColumnItem); +const DragHandle = SortableHandle(() => <Grabber style={{ width: 10 }} />); + +const COLUMN_ORDERS = { + database: t`Database`, + alphabetical: t`Alphabetical`, + custom: t`Custom`, + smart: t`Smart`, +}; + +class ColumnOrderDropdown extends Component { + handleSelect = ({ value }) => { + this.props.table.update({ field_order: value }); + this._popover.close(); + }; + + render() { + const { table } = this.props; + const items = Object.entries(COLUMN_ORDERS).map(([value, name]) => ({ + value, + name, + })); + return ( + <PopoverWithTrigger + ref={ref => (this._popover = ref)} + triggerElement={ + <span + className="text-brand text-bold" + style={{ textTransform: "none", letterSpacing: 0 }} + > + {t`Column order: ${COLUMN_ORDERS[table.field_order]}`} + <Icon + className="ml1" + name="chevrondown" + size={12} + style={{ transform: "translateY(2px)" }} + /> + </span> + } + > + <AccordionList + className="text-brand" + sections={[{ items }]} + alwaysExpanded + onChange={this.handleSelect} + itemIsSelected={({ value }) => value === table.field_order} + /> + </PopoverWithTrigger> + ); + } +} diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx index 2b53ff7f2d43143404df9e3a356b44ecf37fce94..a46302579de95178ae18117ff11e102733676516 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable.jsx @@ -149,7 +149,7 @@ export default class MetadataTable extends Component { <MetricsList onRetire={onRetireMetric} tableMetadata={table} /> {this.props.idfields && ( <ColumnsList - fields={table.fields} + table={table} updateField={this.props.updateField} idfields={this.props.idfields} /> diff --git a/frontend/src/metabase/components/Grabber.jsx b/frontend/src/metabase/components/Grabber.jsx new file mode 100644 index 0000000000000000000000000000000000000000..891956607871dce95dab997b18a306f2b3ac4221 --- /dev/null +++ b/frontend/src/metabase/components/Grabber.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import cx from "classnames"; + +export default function Grabber({ className, style }) { + return <div className={cx("Grabber cursor-grab", className)} style={style} />; +} diff --git a/frontend/src/metabase/css/admin.css b/frontend/src/metabase/css/admin.css index 92d59a43e34563d8f02246b3e76c09fce8168064..b5f0fb118a2bd44785b25081299ce7ebd80191d1 100644 --- a/frontend/src/metabase/css/admin.css +++ b/frontend/src/metabase/css/admin.css @@ -327,6 +327,10 @@ opacity: 1; } +.ColumnSortHelper { + box-shadow: 0 7px 20px var(--color-shadow); +} + #formField-details-port .Form-input, #formField-details-tunnel-port .Form-input { max-width: 200px !important; diff --git a/frontend/src/metabase/css/components/grabber.css b/frontend/src/metabase/css/components/grabber.css new file mode 100644 index 0000000000000000000000000000000000000000..c28f514034d5d58badfe41e284d529981f03e51d --- /dev/null +++ b/frontend/src/metabase/css/components/grabber.css @@ -0,0 +1,14 @@ +.Grabber { + /* This image is a svg circle as a data url. + We tile it for the whole div. */ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle fill='%23E3E7E9' r='1.5' cx='2.5' cy='2.5' /%3E%3C/svg%3E"); + background-repeat: round round; + background-size: 5px 5px; +} + +/* .sort-helper isn't used in <Grabber>. You'll need to set that on a parent being sorted. */ +.ColumnSortHelper .Grabber, +.Grabber:hover { + /* This image is the same but with a different fill color. */ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle fill='%23C7CFD4' r='1.5' cx='2.5' cy='2.5' /%3E%3C/svg%3E"); +} diff --git a/frontend/src/metabase/css/index.css b/frontend/src/metabase/css/index.css index be06de40862a04545b82201f09f0343a1bd5f61b..c63bdb30adc88aca7906ef2836476a23319186a2 100644 --- a/frontend/src/metabase/css/index.css +++ b/frontend/src/metabase/css/index.css @@ -10,6 +10,7 @@ @import "./components/modal.css"; @import "./components/select.css"; @import "./components/table.css"; +@import "./components/grabber.css"; @import "./containers/entity_search.css"; diff --git a/frontend/src/metabase/entities/tables.js b/frontend/src/metabase/entities/tables.js index 4402c8bf359585d16e860b489f8b160b797534c0..979dcbec07df159cac251d65cde15aa772356371 100644 --- a/frontend/src/metabase/entities/tables.js +++ b/frontend/src/metabase/entities/tables.js @@ -31,6 +31,7 @@ const listTablesForDatabase = async (...args) => // HACK: no /api/database/:dbId/tables endpoint (await GET("/api/database/:dbId/metadata")(...args)).tables; const listTablesForSchema = GET("/api/database/:dbId/schema/:schemaName"); +const updateFieldOrder = PUT("/api/table/:id/fields/order"); const updateTables = PUT("/api/table"); // OBJECT ACTIONS @@ -39,6 +40,7 @@ export const FETCH_METADATA = "metabase/entities/FETCH_METADATA"; export const FETCH_TABLE_METADATA = "metabase/entities/FETCH_TABLE_METADATA"; export const FETCH_TABLE_FOREIGN_KEYS = "metabase/entities/FETCH_TABLE_FOREIGN_KEYS"; +const UPDATE_TABLE_FIELD_ORDER = "metabase/entities/UPDATE_TABLE_FIELD_ORDER"; const Tables = createEntity({ name: "tables", @@ -113,6 +115,11 @@ const Tables = createEntity({ const fks = await MetabaseApi.table_fks({ tableId: entityObject.id }); return { id: entityObject.id, fks: fks }; }), + + setFieldOrder: compose(withAction(UPDATE_TABLE_FIELD_ORDER))( + ({ id }, fieldOrder) => (dispatch, getState) => + updateFieldOrder({ id, fieldOrder }, { bodyParamName: "fieldOrder" }), + ), }, // FORMS diff --git a/frontend/src/metabase/lib/api.js b/frontend/src/metabase/lib/api.js index c26ef9c1ac262da4dff4445c614506fee5de72f6..c3073ee7fe50eefc64d1178a877a53173edd82fb 100644 --- a/frontend/src/metabase/lib/api.js +++ b/frontend/src/metabase/lib/api.js @@ -20,6 +20,7 @@ export type Options = { raw?: { [key: string]: boolean }, headers?: { [key: string]: string }, hasBody?: boolean, + bodyParamName?: string, }; const ONE_SECOND = 1000; @@ -124,7 +125,9 @@ export class Api extends EventEmitter { let body; if (options.hasBody) { - body = JSON.stringify(data); + body = JSON.stringify( + options.bodyParamName != null ? data[options.bodyParamName] : data, + ); } else { const qs = querystring.stringify(data); if (qs) { diff --git a/frontend/src/metabase/reference/databases/FieldList.jsx b/frontend/src/metabase/reference/databases/FieldList.jsx index 467828d4dfd03c95bbdc25cf02d4be7b92008e54..df50df3fa58711bf27d2f5c65aa0b137498ed0f6 100644 --- a/frontend/src/metabase/reference/databases/FieldList.jsx +++ b/frontend/src/metabase/reference/databases/FieldList.jsx @@ -161,23 +161,26 @@ export default class FieldList extends Component { </div> </div> <List> - {Object.values(entities).map( - entity => - entity && - entity.id && - entity.name && ( - <li key={entity.id}> - <Field - field={entity} - foreignKeys={foreignKeys} - url={`/reference/databases/${table.db_id}/tables/${table.id}/fields/${entity.id}`} - icon={getIconForField(entity)} - isEditing={isEditing} - formField={fields[entity.id]} - /> - </li> - ), - )} + {Object.values(entities) + // respect the column sort order + .sort((a, b) => a.position - b.position) + .map( + entity => + entity && + entity.id && + entity.name && ( + <li key={entity.id}> + <Field + field={entity} + foreignKeys={foreignKeys} + url={`/reference/databases/${table.db_id}/tables/${table.id}/fields/${entity.id}`} + icon={getIconForField(entity)} + isEditing={isEditing} + formField={fields[entity.id]} + /> + </li> + ), + )} </List> </div> </div> diff --git a/frontend/test/__support__/cypress.js b/frontend/test/__support__/cypress.js index 075eecfc9986c4f4bac294e7b2ef1453f4f7bc38..8c583cb3af57183fc3f80bc0abc4f7e4c06b032f 100644 --- a/frontend/test/__support__/cypress.js +++ b/frontend/test/__support__/cypress.js @@ -88,3 +88,24 @@ export function typeAndBlurUsingLabel(label, value) { } Cypress.on("uncaught:exception", (err, runnable) => false); + +export function withSampleDataset(f) { + cy.request("GET", "/api/database/1/metadata").then(({ body }) => { + const SAMPLE_DATASET = {}; + for (const table of body.tables) { + const fields = {}; + for (const field of table.fields) { + fields[field.name] = field.id; + } + SAMPLE_DATASET[table.name] = fields; + SAMPLE_DATASET[table.name + "_ID"] = table.id; + } + f(SAMPLE_DATASET); + }); +} + +export function visitAlias(alias) { + cy.get(alias).then(url => { + cy.visit(url); + }); +} diff --git a/frontend/test/metabase/lib/api.unit.spec.js b/frontend/test/metabase/lib/api.unit.spec.js index 59e551b88524ae8ef43beca27dfd855c4547cbee..419c6e1fb38a3796c96dd96440b6657fe80c59bd 100644 --- a/frontend/test/metabase/lib/api.unit.spec.js +++ b/frontend/test/metabase/lib/api.unit.spec.js @@ -1,4 +1,4 @@ -import api, { GET, POST } from "metabase/lib/api"; +import api, { GET, POST, PUT } from "metabase/lib/api"; api.basename = ""; import mock, { once } from "xhr-mock"; @@ -38,6 +38,27 @@ describe("api", () => { expect(response).toEqual({ status: "ok" }); }); + it("should PUT with remaining params as body", async () => { + expect.assertions(1); + mock.put("/hello/123", (req, res) => { + expect(req.body()).toEqual('{"other":"stuff"}'); + return res.status(201); + }); + await PUT("/hello/:id")({ id: 123, other: "stuff" }); + }); + + it("should PUT with a specific params as the body", async () => { + expect.assertions(1); + mock.put("/hello/123", (req, res) => { + expect(req.body()).toEqual('["i","am","an","array"]'); + return res.status(201); + }); + await PUT("/hello/:id")( + { id: 123, notAnObject: ["i", "am", "an", "array"] }, + { bodyParamName: "notAnObject" }, + ); + }); + it("POST should throw on 503 with no retry", async () => { expect.assertions(1); mock.post("/hello", capacityResponse); diff --git a/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js index d09e162f4a1cb4d385fcf3e25d4bb7abb71c1608..f5d313c1612f038e662459daf34d2bc9c5f122fb 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/field.cy.spec.js @@ -1,12 +1,20 @@ -import { signInAsAdmin, restore } from "__support__/cypress"; - -const ORDERS_CREATED_AT_URL = "/admin/datamodel/database/1/table/2/15/general"; -const ORDERS_PRODUCT_URL = "/admin/datamodel/database/1/table/2/11/general"; -const ORDERS_QUANTITY_URL = "/admin/datamodel/database/1/table/2/14/general"; +import { + signInAsAdmin, + restore, + withSampleDataset, + visitAlias, +} from "__support__/cypress"; describe("scenarios > admin > datamodel > field", () => { beforeEach(() => { signInAsAdmin(); + withSampleDataset(({ ORDERS, ORDERS_ID }) => { + ["CREATED_AT", "PRODUCT_ID", "QUANTITY"].forEach(name => { + cy.wrap( + `/admin/datamodel/database/1/table/${ORDERS_ID}/${ORDERS[name]}/general`, + ).as(`ORDERS_${name}_URL`); + }); + }); cy.server(); cy.route("PUT", "/api/field/*").as("fieldUpdate"); cy.route("POST", "/api/field/*/dimension").as("fieldDimensionUpdate"); @@ -17,7 +25,7 @@ describe("scenarios > admin > datamodel > field", () => { before(restore); it("lets you change field name and description", () => { - cy.visit(ORDERS_CREATED_AT_URL); + visitAlias("@ORDERS_CREATED_AT_URL"); cy.get('input[name="display_name"]').as("display_name"); cy.get('input[name="description"]').as("description"); @@ -49,7 +57,7 @@ describe("scenarios > admin > datamodel > field", () => { before(restore); it("lets you change field visibility", () => { - cy.visit(ORDERS_CREATED_AT_URL); + visitAlias("@ORDERS_CREATED_AT_URL"); cy.contains("Everywhere").click(); cy.contains("Do not include").click({ force: true }); @@ -64,7 +72,7 @@ describe("scenarios > admin > datamodel > field", () => { before(restore); it("lets you change the type to 'No special type'", () => { - cy.visit(ORDERS_PRODUCT_URL); + visitAlias("@ORDERS_PRODUCT_ID_URL"); cy.contains("Foreign Key").click(); cy.contains("No special type").click({ force: true }); @@ -75,7 +83,7 @@ describe("scenarios > admin > datamodel > field", () => { }); it("lets you change the type to 'Number'", () => { - cy.visit(ORDERS_PRODUCT_URL); + visitAlias("@ORDERS_PRODUCT_ID_URL"); cy.contains("No special type").click(); cy.contains("Number").click({ force: true }); @@ -86,7 +94,7 @@ describe("scenarios > admin > datamodel > field", () => { }); it("lets you change the type to 'Foreign key' and choose the target field", () => { - cy.visit(ORDERS_PRODUCT_URL); + visitAlias("@ORDERS_PRODUCT_ID_URL"); cy.contains("Number").click(); cy.get(".ReactVirtualized__Grid").scrollTo(0, 0); // HACK: scroll to the top of the list. Ideally we should probably disable AccordianList virtualization @@ -107,7 +115,7 @@ describe("scenarios > admin > datamodel > field", () => { before(restore); it("lets you change to 'Search box'", () => { - cy.visit(ORDERS_QUANTITY_URL); + visitAlias("@ORDERS_QUANTITY_URL"); cy.contains("A list of all values").click(); cy.contains("Search box").click(); @@ -122,7 +130,7 @@ describe("scenarios > admin > datamodel > field", () => { before(restore); it("lets you change to 'Use foreign key' and change the target for field with fk", () => { - cy.visit(ORDERS_PRODUCT_URL); + visitAlias("@ORDERS_PRODUCT_ID_URL"); cy.contains("Use original value").click(); cy.contains("Use foreign key").click(); @@ -135,7 +143,7 @@ describe("scenarios > admin > datamodel > field", () => { }); it("lets you change to 'Custom mapping' and set custom values", () => { - cy.visit(ORDERS_QUANTITY_URL); + visitAlias("@ORDERS_QUANTITY_URL"); cy.contains("Use original value").click(); cy.contains("Custom mapping").click(); diff --git a/frontend/test/metabase/scenarios/admin/datamodel/table.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/table.cy.spec.js index 01f121ee335451de21e72d658ec05a4f8f260a62..0a3ad1d6b70c21253830682bae5e986384650f7f 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/table.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/table.cy.spec.js @@ -1,12 +1,20 @@ -import { signInAsAdmin, restore } from "__support__/cypress"; +import { + signInAsAdmin, + restore, + popover, + visitAlias, + withSampleDataset, +} from "__support__/cypress"; const SAMPLE_DB_URL = "/admin/datamodel/database/1"; -const ORDERS_URL = `${SAMPLE_DB_URL}/table/2`; describe("scenarios > admin > datamodel > table", () => { beforeEach(() => { restore(); signInAsAdmin(); + withSampleDataset(({ ORDERS_ID }) => { + cy.wrap(`${SAMPLE_DB_URL}/table/${ORDERS_ID}`).as(`ORDERS_URL`); + }); cy.server(); cy.route("PUT", "/api/table/*").as("tableUpdate"); cy.route("PUT", "/api/field/*").as("fieldUpdate"); @@ -18,7 +26,7 @@ describe("scenarios > admin > datamodel > table", () => { "GET", "/api/table/2/query_metadata?include_sensitive_fields=true", ).as("tableMetadataFetch"); - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); cy.get('input[name="display_name"]').as("display_name"); cy.get('input[name="description"]').as("description"); @@ -53,7 +61,7 @@ describe("scenarios > admin > datamodel > table", () => { }); it("shouild allow changing the visibility and reason", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); // visibility cy.contains(/^Queryable$/).should("have.class", "text-brand"); @@ -91,7 +99,7 @@ describe("scenarios > admin > datamodel > table", () => { cy.get(alias) .contains(initialOption) .click({ force: true }); - cy.get(".PopoverBody") + popover() .contains(desiredOption) .click({ force: true }); cy.get(alias).contains(desiredOption); @@ -103,14 +111,14 @@ describe("scenarios > admin > datamodel > table", () => { } it("should allow hiding of columns outside of detail views", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); field("Created At").as("created_at"); testSelect("@created_at", "Everywhere", "Only in detail views"); }); it("should allow hiding of columns entirely", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); field("Created At").as("created_at"); testSelect("@created_at", "Everywhere", "Do not include"); @@ -125,7 +133,7 @@ describe("scenarios > admin > datamodel > table", () => { }); it("should allow changing of special type and currency", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); field("Tax").as("tax"); testSelect("@tax", "No special type", "Currency"); @@ -133,14 +141,14 @@ describe("scenarios > admin > datamodel > table", () => { }); it("should allow changing of foreign key target", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); field("User ID").as("user_id"); testSelect("@user_id", "People → ID", "Products → ID"); }); it("should allow creating segments", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); cy.contains("Add a Segment").click(); @@ -166,7 +174,7 @@ describe("scenarios > admin > datamodel > table", () => { }); it("should allow creating metrics", () => { - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); cy.contains("Add a Metric").click(); @@ -186,9 +194,40 @@ describe("scenarios > admin > datamodel > table", () => { cy.contains("Revenue"); }); + it("should allow sorting columns", () => { + visitAlias("@ORDERS_URL"); + cy.contains("Column order:").click(); + + // switch to alphabetical ordering + popover() + .contains("Alphabetical") + .click({ force: true }); + + // move product_id to the top + cy.route("PUT", "/api/table/2/fields/order").as("fieldReorder"); + cy.get(".Grabber") + .eq(3) + .trigger("mousedown", 0, 0); + cy.get("#ColumnsList") + .trigger("mousemove", 10, 10) + .trigger("mouseup", 10, 10); + + // wait for request to complete + cy.wait("@fieldReorder"); + + // check that new order is obeyed in queries + cy.request("POST", "/api/dataset", { + database: 1, + query: { "source-table": 2 }, + type: "query", + }).then(resp => { + expect(resp.body.data.cols[0].name).to.eq("PRODUCT_ID"); + }); + }); + it("should allow bulk hiding tables", () => { cy.route("GET", `**/api/table/*/query_metadata*`).as("tableMetadata"); - cy.visit(ORDERS_URL); + visitAlias("@ORDERS_URL"); cy.wait(["@tableMetadata", "@tableMetadata", "@tableMetadata"]); // wait for these api calls to finish to avoid them overwriting later PUT calls cy.contains("4 Queryable Tables"); diff --git a/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js index 4850a3c2d16d6f9309c9c5d758cf58b362ceb527..f6f51c14088c8a88ae4dee122221bf59a60d0179 100644 --- a/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js @@ -1,4 +1,10 @@ -import { signInAsAdmin, signOut, restore, popover } from "__support__/cypress"; +import { + signInAsAdmin, + signOut, + restore, + popover, + withSampleDataset, +} from "__support__/cypress"; const METABASE_SECRET_KEY = "24134bd93e081773fb178e8e1abb4e8a973822f7e19c872bd92c8d5a122ef63f"; @@ -9,9 +15,6 @@ const QUESTION_JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZSI6eyJxdWVzdGlvbiI6M30sInBhcmFtcyI6e30sImlhdCI6MTU3OTU1OTg3NH0.alV205oYgfyWuwLNQSLVgfHop1tpevX4C26Xal-bia8"; const DASHBOARD_JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZSI6eyJkYXNoYm9hcmQiOjJ9LCJwYXJhbXMiOnt9LCJpYXQiOjE1Nzk1NjAxMTF9.LjOiTp4p2lV3b2VpSjcg0GuSaE2O0xhHwc59JDYcBJI"; -const ORDER_USER_ID_FIELD_ID = 9; -const PEOPLE_NAME_FIELD_ID = 20; -const PEOPLE_ID_FIELD_ID = 21; // NOTE: some overlap with parameters.cy.spec.js @@ -22,26 +25,30 @@ describe("scenarios > dashboard > parameters-embedded", () => { restore(); signInAsAdmin(); - createQuestion().then(res => { - questionId = res.body.id; - createDashboard().then(res => { - dashboardId = res.body.id; - addCardToDashboard({ dashboardId, questionId }).then(res => { - dashcardId = res.body.id; - mapParameters({ dashboardId, questionId, dashcardId }); - }); + withSampleDataset(SAMPLE_DATASET => { + cy.log(SAMPLE_DATASET); + const { ORDERS, PEOPLE } = SAMPLE_DATASET; + cy.request("POST", `/api/field/${ORDERS.USER_ID}/dimension`, { + type: "external", + name: "User ID", + human_readable_field_id: PEOPLE.NAME, }); - }); - cy.request("POST", `/api/field/${ORDER_USER_ID_FIELD_ID}/dimension`, { - type: "external", - name: "User ID", - human_readable_field_id: PEOPLE_NAME_FIELD_ID, - }); - [ORDER_USER_ID_FIELD_ID, PEOPLE_NAME_FIELD_ID, PEOPLE_ID_FIELD_ID].forEach( - id => + [ORDERS.USER_ID, PEOPLE.NAME, PEOPLE.ID].forEach(id => cy.request("PUT", `/api/field/${id}`, { has_field_values: "search" }), - ); + ); + + createQuestion(SAMPLE_DATASET).then(res => { + questionId = res.body.id; + createDashboard().then(res => { + dashboardId = res.body.id; + addCardToDashboard({ dashboardId, questionId }).then(res => { + dashcardId = res.body.id; + mapParameters({ dashboardId, questionId, dashcardId }); + }); + }); + }); + }); cy.request("PUT", `/api/setting/embedding-secret-key`, { value: METABASE_SECRET_KEY, @@ -197,7 +204,7 @@ function sharedParametersTests(visitUrl) { }); } -const createQuestion = () => +const createQuestion = ({ ORDERS, PEOPLE }) => cy.request("PUT", "/api/card/3", { name: "Test Question", dataset_query: { @@ -211,7 +218,7 @@ const createQuestion = () => name: "id", display_name: "Id", type: "dimension", - dimension: ["field-id", 21], + dimension: ["field-id", PEOPLE.ID], "widget-type": "id", default: null, }, @@ -220,7 +227,7 @@ const createQuestion = () => name: "name", display_name: "Name", type: "dimension", - dimension: ["field-id", 20], + dimension: ["field-id", PEOPLE.NAME], "widget-type": "category", default: null, }, @@ -229,7 +236,7 @@ const createQuestion = () => name: "source", display_name: "Source", type: "dimension", - dimension: ["field-id", 24], + dimension: ["field-id", PEOPLE.SOURCE], "widget-type": "category", default: null, }, @@ -238,7 +245,7 @@ const createQuestion = () => name: "user_id", display_name: "User", type: "dimension", - dimension: ["field-id", 9], + dimension: ["field-id", ORDERS.USER_ID], "widget-type": "id", default: null, }, diff --git a/frontend/test/metabase/scenarios/public/public.cy.spec.js b/frontend/test/metabase/scenarios/public/public.cy.spec.js index 73832f58d176d7d12f0f709d53d6630113d3bee2..e679c6409d908d11a6aca0737ddd351b5f412582 100644 --- a/frontend/test/metabase/scenarios/public/public.cy.spec.js +++ b/frontend/test/metabase/scenarios/public/public.cy.spec.js @@ -5,6 +5,7 @@ import { restore, popover, modal, + withSampleDataset, } from "__support__/cypress"; const COUNT_ALL = "200"; @@ -23,31 +24,37 @@ describe("scenarios > public", () => { before(() => { restore(); signInAsAdmin(); - cy.request("POST", "/api/card", { - name: "sql param", - dataset_query: { - type: "native", - native: { - query: "select count(*) from products where {{c}}", - "template-tags": { - c: { - id: "e116f242-fbaa-1feb-7331-21ac59f021cc", - name: "c", - "display-name": "Category", - type: "dimension", - dimension: ["field-id", 6], - default: null, - "widget-type": "category", + + // setup parameterized question + withSampleDataset(({ PRODUCTS }) => + cy + .request("POST", "/api/card", { + name: "sql param", + dataset_query: { + type: "native", + native: { + query: "select count(*) from products where {{c}}", + "template-tags": { + c: { + id: "e126f242-fbaa-1feb-7331-21ac59f021cc", + name: "c", + "display-name": "Category", + type: "dimension", + dimension: ["field-id", PRODUCTS.CATEGORY], + default: null, + "widget-type": "category", + }, + }, }, + database: 1, }, - }, - database: 1, - }, - display: "scalar", - visualization_settings: {}, - }).then(({ body }) => { - questionId = body.id; - }); + display: "scalar", + visualization_settings: {}, + }) + .then(({ body }) => { + questionId = body.id; + }), + ); }); beforeEach(() => { diff --git a/frontend/test/metabase/scenarios/question/native.cy.spec.js b/frontend/test/metabase/scenarios/question/native.cy.spec.js index c48ceb0c972b1ddc94c5928e1900e37dd9e7ca25..cca6f1b4cf20d88f1b63ca2c44eb8752b2ecb46d 100644 --- a/frontend/test/metabase/scenarios/question/native.cy.spec.js +++ b/frontend/test/metabase/scenarios/question/native.cy.spec.js @@ -4,6 +4,7 @@ import { restore, popover, modal, + withSampleDataset, } from "__support__/cypress"; describe("scenarios > question > native", () => { @@ -187,34 +188,36 @@ describe("scenarios > question > native", () => { }); it("can load a question with a date filter (from issue metabase#12228)", () => { - cy.request("POST", "/api/card", { - name: "Test Question", - dataset_query: { - type: "native", - native: { - query: "select count(*) from orders where {{created_at}}", - "template-tags": { - created_at: { - id: "6b8b10ef-0104-1047-1e1b-2492d5954322", - name: "created_at", - "display-name": "Created at", - type: "dimension", - dimension: ["field-id", 15], - "widget-type": "date/month-year", + withSampleDataset(({ ORDERS }) => { + cy.request("POST", "/api/card", { + name: "Test Question", + dataset_query: { + type: "native", + native: { + query: "select count(*) from orders where {{created_at}}", + "template-tags": { + created_at: { + id: "6b8b10ef-0104-1047-1e1b-2492d5954322", + name: "created_at", + "display-name": "Created at", + type: "dimension", + dimension: ["field-id", ORDERS.CREATED_AT], + "widget-type": "date/month-year", + }, }, }, + database: 1, }, - database: 1, - }, - display: "scalar", - description: null, - visualization_settings: {}, - collection_id: null, - result_metadata: null, - metadata_checksum: null, - }).then(response => { - cy.visit(`/question/${response.body.id}?created_at=2020-01`); - cy.contains("580"); + display: "scalar", + description: null, + visualization_settings: {}, + collection_id: null, + result_metadata: null, + metadata_checksum: null, + }).then(response => { + cy.visit(`/question/${response.body.id}?created_at=2020-01`); + cy.contains("580"); + }); }); }); diff --git a/frontend/test/metabase/scenarios/visualizations/chart_drill.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/chart_drill.cy.spec.js index e29d95f0e838da9525e7703c6b907a191a0cdcdf..9852d2df7773fcf2cc4d391998869a2e279e9ff6 100644 --- a/frontend/test/metabase/scenarios/visualizations/chart_drill.cy.spec.js +++ b/frontend/test/metabase/scenarios/visualizations/chart_drill.cy.spec.js @@ -1,54 +1,64 @@ -import { signInAsAdmin, restore } from "__support__/cypress"; +import { signInAsAdmin, restore, withSampleDataset } from "__support__/cypress"; describe("scenarios > visualizations > chart drill", () => { before(restore); beforeEach(signInAsAdmin); it("should allow brush date filter", () => { - cy.request("POST", "/api/card", { - name: "Orders by Product → Created At (month) and Product → Category", - dataset_query: { - database: 1, - query: { - "source-table": 2, - aggregation: [["count"]], - breakout: [ - [ - "datetime-field", - ["fk->", ["field-id", 11], ["field-id", 7]], - "month", + withSampleDataset(({ ORDERS, PRODUCTS }) => { + cy.request("POST", "/api/card", { + name: "Orders by Product → Created At (month) and Product → Category", + dataset_query: { + database: 1, + query: { + "source-table": 2, + aggregation: [["count"]], + breakout: [ + [ + "datetime-field", + [ + "fk->", + ["field-id", ORDERS.PRODUCT_ID], + ["field-id", PRODUCTS.CREATED_AT], + ], + "month", + ], + [ + "fk->", + ["field-id", ORDERS.PRODUCT_ID], + ["field-id", PRODUCTS.CATEGORY], + ], ], - ["fk->", ["field-id", 11], ["field-id", 6]], - ], + }, + type: "query", }, - type: "query", - }, - display: "line", - visualization_settings: {}, - }).then(response => { - cy.visit(`/question/${response.body.id}`); + display: "line", + visualization_settings: {}, + }).then(response => { + cy.visit(`/question/${response.body.id}`); - // wait for chart to expand and display legend/labels - cy.contains("Loading..."); // this gives more time to load - cy.contains("Gadget"); - cy.contains("January, 2017"); - cy.wait(100); // wait longer to avoid grabbing the svg before a chart redraw + // wait for chart to expand and display legend/labels + cy.contains("Loading..."); // this gives more time to load + cy.contains("Gadget"); + cy.contains("January, 2017"); + cy.wait(100); // wait longer to avoid grabbing the svg before a chart redraw - // drag across to filter - cy.get(".dc-chart svg") - .trigger("mousedown", 100, 200) - .trigger("mousemove", 200, 200) - .trigger("mouseup", 200, 200); + // drag across to filter + cy.get(".dc-chart svg") + .trigger("mousedown", 100, 200) + .trigger("mousemove", 200, 200) + .trigger("mouseup", 200, 200); - // new filter applied - cy.contains("Created At between May, 2016 July, 2016"); - // more granular axis labels - cy.contains("June, 2016"); - // confirm that product category is still broken out - cy.contains("Gadget"); - cy.contains("Doohickey"); - cy.contains("Gizmo"); - cy.contains("Widget"); + // new filter applied + cy.contains("Created At between May, 2016 July, 2016"); + // more granular axis labels + cy.contains("June, 2016"); + // confirm that product category is still broken out + cy.contains("Gadget"); + cy.contains("Doohickey"); + cy.contains("Gizmo"); + cy.contains("Widget"); + }); }); }); diff --git a/frontend/test/snapshot-creators/default.cy.snap.js b/frontend/test/snapshot-creators/default.cy.snap.js index 9ab7de9ceee544019bba06a89f2ed98306cfb8f5..9af881e279422f80f2642ad5429afb0bcf2c3e8e 100644 --- a/frontend/test/snapshot-creators/default.cy.snap.js +++ b/frontend/test/snapshot-creators/default.cy.snap.js @@ -1,4 +1,9 @@ -import { snapshot, restore, USERS } from "__support__/cypress"; +import { + snapshot, + restore, + USERS, + withSampleDataset, +} from "__support__/cypress"; describe("default", () => { it("default", () => { @@ -6,7 +11,9 @@ describe("default", () => { setup(); updateSettings(); addUsersAndGroups(); - createQuestionAndDashboard(); + withSampleDataset(SAMPLE_DATASET => { + createQuestionAndDashboard(SAMPLE_DATASET); + }); snapshot("default"); restore("blank"); }); @@ -100,13 +107,17 @@ function addUsersAndGroups() { }); } -function createQuestionAndDashboard() { +function createQuestionAndDashboard({ ORDERS, ORDERS_ID }) { // question 1: Orders cy.request("POST", "/api/card", { name: "Orders", display: "table", visualization_settings: {}, - dataset_query: { database: 1, query: { "source-table": 2 }, type: "query" }, + dataset_query: { + database: 1, + query: { "source-table": ORDERS_ID }, + type: "query", + }, }); // question 2: Orders, Count @@ -116,7 +127,7 @@ function createQuestionAndDashboard() { visualization_settings: {}, dataset_query: { database: 1, - query: { "source-table": 2, aggregation: [["count"]] }, + query: { "source-table": ORDERS_ID, aggregation: [["count"]] }, type: "query", }, }); @@ -126,9 +137,9 @@ function createQuestionAndDashboard() { dataset_query: { type: "query", query: { - "source-table": 2, + "source-table": ORDERS_ID, aggregation: [["count"]], - breakout: [["datetime-field", ["field-id", 15], "year"]], + breakout: [["datetime-field", ["field-id", ORDERS.CREATED_AT], "year"]], }, database: 1, }, diff --git a/modules/drivers/bigquery/project.clj b/modules/drivers/bigquery/project.clj index 5f07b7581359007666541fe128f4e097d238c697..c7173597d6118496cea33662c698666eb8a491a2 100644 --- a/modules/drivers/bigquery/project.clj +++ b/modules/drivers/bigquery/project.clj @@ -2,7 +2,7 @@ :min-lein-version "2.5.0" :dependencies - [[com.google.apis/google-api-services-bigquery "v2-rev20200429-1.30.9"]] + [[com.google.apis/google-api-services-bigquery "v2-rev20200523-1.30.9"]] :profiles {:provided diff --git a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj index c64491c1864ae3fa044fc7da2e7a1e4281ba55d8..513a903587e13b33f53cecf8891e3c9a3a507d3f 100644 --- a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj +++ b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj @@ -3,6 +3,7 @@ [set :as set] [string :as str]] [clojure.tools.logging :as log] + [medley.core :as m] [metabase [driver :as driver] [util :as u]] @@ -124,10 +125,11 @@ (s/defn ^:private table-schema->metabase-field-info [schema :- TableSchema] - (for [^TableFieldSchema field (.getFields schema)] - {:name (.getName field) - :database-type (.getType field) - :base-type (bigquery-type->base-type (.getType field))})) + (for [[idx ^TableFieldSchema field] (m/indexed (.getFields schema))] + {:name (.getName field) + :database-type (.getType field) + :base-type (bigquery-type->base-type (.getType field)) + :database-position idx})) (defmethod driver/describe-table :bigquery [_ database {table-name :name}] @@ -174,7 +176,10 @@ ~@body))) (defn- post-process-native - "Parse results of a BigQuery query." + "Parse results of a BigQuery query. `respond` is the same function passed to + `metabase.driver/execute-reducible-query`, and has the signature + + (respond results-metadata rows)" [respond ^QueryResponse resp] (with-finished-response [response resp] (let [^TableSchema schema @@ -191,7 +196,7 @@ (for [column (table-schema->metabase-field-info schema)] (-> column (set/rename-keys {:base-type :base_type}) - (dissoc :database-type)))] + (dissoc :database-type :database-position)))] (respond {:cols columns} (for [^TableRow row (.getRows response)] diff --git a/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj b/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj index daafef1a8a520b79ee6c96d81bfef0a4845a2ca8..53b07e2b8793c0d232ef0bb99ccb02fe9d0db5e1 100644 --- a/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj +++ b/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj @@ -25,44 +25,41 @@ (deftest native-query-test (mt/test-driver :bigquery - (is (= [[100] - [99]] - (get-in - (qp/process-query - {:native {:query (str "SELECT `test_data.venues`.`id` " - "FROM `test_data.venues` " - "ORDER BY `test_data.venues`.`id` DESC " - "LIMIT 2;")} - :type :native - :database (mt/id)}) - [:data :rows]))) - - (is (= [{:name "venue_id" - :display_name "venue_id" - :source :native - :base_type :type/Integer - :field_ref [:field-literal "venue_id" :type/Integer]} - {:name "user_id" - :display_name "user_id" - :source :native - :base_type :type/Integer - :field_ref [:field-literal "user_id" :type/Integer]} - {:name "checkins_id" - :display_name "checkins_id" - :source :native - :base_type :type/Integer - :field_ref [:field-literal "checkins_id" :type/Integer]}] - (qp.test/cols + (is (= [[100] [99]] + (mt/rows (qp/process-query - {:native {:query (str "SELECT `test_data.checkins`.`venue_id` AS `venue_id`, " - " `test_data.checkins`.`user_id` AS `user_id`, " - " `test_data.checkins`.`id` AS `checkins_id` " - "FROM `test_data.checkins` " - "LIMIT 2")} - :type :native - :database (mt/id)}))) - (str "make sure that BigQuery native queries maintain the column ordering specified in the SQL -- " - "post-processing ordering shouldn't apply (Issue #2821)")))) + (mt/native-query + {:query (str "SELECT `v2_test_data.venues`.`id` " + "FROM `v2_test_data.venues` " + "ORDER BY `v2_test_data.venues`.`id` DESC " + "LIMIT 2;")}))))) + + (testing (str "make sure that BigQuery native queries maintain the column ordering specified in the SQL -- " + "post-processing ordering shouldn't apply (Issue #2821)") + (is (= [{:name "venue_id" + :display_name "venue_id" + :source :native + :base_type :type/Integer + :field_ref [:field-literal "venue_id" :type/Integer]} + {:name "user_id" + :display_name "user_id" + :source :native + :base_type :type/Integer + :field_ref [:field-literal "user_id" :type/Integer]} + {:name "checkins_id" + :display_name "checkins_id" + :source :native + :base_type :type/Integer + :field_ref [:field-literal "checkins_id" :type/Integer]}] + (qp.test/cols + (qp/process-query + {:native {:query (str "SELECT `v2_test_data.checkins`.`venue_id` AS `venue_id`, " + " `v2_test_data.checkins`.`user_id` AS `user_id`, " + " `v2_test_data.checkins`.`id` AS `checkins_id` " + "FROM `v2_test_data.checkins` " + "LIMIT 2")} + :type :native + :database (mt/id)}))))))) (deftest aggregations-test (mt/test-driver :bigquery @@ -85,11 +82,11 @@ rows)))) (testing "let's make sure we're generating correct HoneySQL + SQL for aggregations" - (is (= {:select [[(hx/identifier :field "test_data.venues" "price") + (is (= {:select [[(hx/identifier :field "v2_test_data.venues" "price") (hx/identifier :field-alias "price")] - [(hsql/call :avg (hx/identifier :field "test_data.venues" "category_id")) + [(hsql/call :avg (hx/identifier :field "v2_test_data.venues" "category_id")) (hx/identifier :field-alias "avg")]] - :from [(hx/identifier :table "test_data.venues")] + :from [(hx/identifier :table "v2_test_data.venues")] :group-by [(hx/identifier :field-alias "price")] :order-by [[(hx/identifier :field-alias "avg") :asc]]} (mt/with-everything-store @@ -100,9 +97,9 @@ :breakout [$price] :order-by [[:asc [:aggregation 0]]]}))))) - (is (= {:query (str "SELECT `test_data.venues`.`price` AS `price`," - " avg(`test_data.venues`.`category_id`) AS `avg` " - "FROM `test_data.venues` " + (is (= {:query (str "SELECT `v2_test_data.venues`.`price` AS `price`," + " avg(`v2_test_data.venues`.`category_id`) AS `avg` " + "FROM `v2_test_data.venues` " "GROUP BY `price` " "ORDER BY `avg` ASC, `price` ASC") :params nil @@ -116,9 +113,9 @@ (mt/test-driver :bigquery (is (= (str "SELECT `categories__via__category_id`.`name` AS `name`," " count(*) AS `count` " - "FROM `test_data.venues` " - "LEFT JOIN `test_data.categories` `categories__via__category_id`" - " ON `test_data.venues`.`category_id` = `categories__via__category_id`.`id` " + "FROM `v2_test_data.venues` " + "LEFT JOIN `v2_test_data.categories` `categories__via__category_id`" + " ON `v2_test_data.venues`.`category_id` = `categories__via__category_id`.`id` " "GROUP BY `name` " "ORDER BY `name` ASC") ;; normally for test purposes BigQuery doesn't support foreign keys so override the function that checks @@ -187,13 +184,14 @@ (mt/test-driver :bigquery (is (= (str "-- Metabase:: userID: 1000 queryType: MBQL queryHash: 01020304\n" - "SELECT `test_data.venues`.`id` AS `id`," - " `test_data.venues`.`name` AS `name`," - " `test_data.venues`.`category_id` AS `category_id`," - " `test_data.venues`.`latitude` AS `latitude`," - " `test_data.venues`.`longitude` AS `longitude`," - " `test_data.venues`.`price` AS `price` " - "FROM `test_data.venues` " + "SELECT" + " `v2_test_data.venues`.`id` AS `id`," + " `v2_test_data.venues`.`name` AS `name`," + " `v2_test_data.venues`.`category_id` AS `category_id`," + " `v2_test_data.venues`.`latitude` AS `latitude`," + " `v2_test_data.venues`.`longitude` AS `longitude`," + " `v2_test_data.venues`.`price` AS `price` " + "FROM `v2_test_data.venues` " "LIMIT 1") (query->native {:database (mt/id) @@ -209,12 +207,11 @@ (is (= [["Red Medicine"]] (qp.test/rows (qp/process-query - {:database (mt/id) - :type :native - :native {:query (str "SELECT `test_data.venues`.`name` AS `name` " - "FROM `test_data.venues` " - "WHERE `test_data.venues`.`name` = ?") - :params ["Red Medicine"]}}))) + (mt/native-query + {:query (str "SELECT `v2_test_data.venues`.`name` AS `name` " + "FROM `v2_test_data.venues` " + "WHERE `v2_test_data.venues`.`name` = ?") + :params ["Red Medicine"]})))) (str "Do we properly unprepare, and can we execute, queries that still have parameters for one reason or " "another? (EE #277)")))) @@ -375,9 +372,9 @@ (t/local-date "2019-11-12")])))) (mt/test-driver :bigquery (mt/with-everything-store - (let [expected ["WHERE `test_data.checkins`.`date` BETWEEN ? AND ?" - (t/zoned-date-time "2019-11-11T00:00Z[UTC]") - (t/zoned-date-time "2019-11-12T00:00Z[UTC]")]] + (let [expected ["WHERE `v2_test_data.checkins`.`date` BETWEEN ? AND ?" + (t/local-date "2019-11-11") + (t/local-date "2019-11-12")]] (testing "Should be able to get temporal type from a FieldInstance" (is (= expected (between->sql [:between @@ -391,7 +388,7 @@ (t/local-date "2019-11-11") (t/local-date "2019-11-12")])))) (testing "Should be able to get temporal type from a wrapped field-id" - (is (= (cons "WHERE timestamp_trunc(`test_data.checkins`.`date`, day) BETWEEN ? AND ?" + (is (= (cons "WHERE date_trunc(`v2_test_data.checkins`.`date`, day) BETWEEN ? AND ?" (rest expected)) (between->sql [:between [:datetime-field [:field-id (mt/id :checkins :date)] :day] @@ -422,14 +419,14 @@ (mt/with-temp-copy-of-db (try (bigquery.tx/execute! - (format "CREATE TABLE `test_data.%s` ( ts TIMESTAMP, dt DATETIME )" table-name)) + (format "CREATE TABLE `v2_test_data.%s` ( ts TIMESTAMP, dt DATETIME )" table-name)) (bigquery.tx/execute! - (format "INSERT INTO `test_data.%s` (ts, dt) VALUES (TIMESTAMP \"2020-01-01 00:00:00 UTC\", DATETIME \"2020-01-01 00:00:00\")" + (format "INSERT INTO `v2_test_data.%s` (ts, dt) VALUES (TIMESTAMP \"2020-01-01 00:00:00 UTC\", DATETIME \"2020-01-01 00:00:00\")" table-name)) (sync/sync-database! (mt/db)) (f table-name) (finally - (bigquery.tx/execute! "DROP TABLE IF EXISTS `test_data.%s`" table-name))))))) + (bigquery.tx/execute! "DROP TABLE IF EXISTS `v2_test_data.%s`" table-name))))))) (deftest filter-by-datetime-timestamp-test (mt/test-driver :bigquery @@ -470,7 +467,7 @@ (qp/process-query {:database (mt/id) :type :native - :native {:query "SELECT count(*) FROM `attempted_murders.attempts` WHERE {{d}}" + :native {:query "SELECT count(*) FROM `v2_attempted_murders.attempts` WHERE {{d}}" :template-tags {"d" {:name "d" :display-name "Date" :type :dimension @@ -611,7 +608,7 @@ (deftest string-escape-test (mt/test-driver :bigquery (testing "Make sure single quotes in parameters are escaped properly to prevent SQL injection\n" - #_(testing "MBQL query" + (testing "MBQL query" (is (= [[0]] (mt/formatted-rows [int] (mt/run-mbql-query venues @@ -623,5 +620,5 @@ (mt/formatted-rows [int] (qp/process-query (mt/native-query - {:query "SELECT count(*) AS `count` FROM `test_data.venues` WHERE `test_data.venues`.`name` = ?" + {:query "SELECT count(*) AS `count` FROM `v2_test_data.venues` WHERE `v2_test_data.venues`.`name` = ?" :params ["x\\\\' OR 1 = 1 -- "]}))))))))) diff --git a/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj b/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj index f5b3a46f1d7fa022f72d4c3bb30521ce58402ee1..2b28ac99a8be5f01e143e86cc4b3dcc58089ee8a 100644 --- a/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj +++ b/modules/drivers/bigquery/test/metabase/driver/bigquery_test.clj @@ -34,11 +34,11 @@ (mt/with-temp-copy-of-db (try (bigquery.tx/execute! - (str "CREATE VIEW `test_data.%s` " + (str "CREATE VIEW `v2_test_data.%s` " "AS " "SELECT v.id AS id, v.name AS venue_name, c.name AS category_name " - "FROM `%s.test_data.venues` v " - "LEFT JOIN `%s.test_data.categories` c " + "FROM `%s.v2_test_data.venues` v " + "LEFT JOIN `%s.v2_test_data.categories` c " "ON v.category_id = c.id " "ORDER BY v.id ASC " "LIMIT 3") @@ -47,7 +47,7 @@ (bigquery.tx/project-id)) (f view-name) (finally - (bigquery.tx/execute! "DROP VIEW IF EXISTS `test_data.%s`" view-name))))))) + (bigquery.tx/execute! "DROP VIEW IF EXISTS `v2_test_data.%s`" view-name))))))) (defmacro ^:private with-view [[view-name-binding] & body] `(do-with-view (fn [~(or view-name-binding '_)] ~@body))) @@ -60,16 +60,16 @@ "`describe-database` should see the view") (is (= {:schema nil :name view-name - :fields #{{:name "id", :database-type "INTEGER", :base-type :type/Integer} - {:name "venue_name", :database-type "STRING", :base-type :type/Text} - {:name "category_name", :database-type "STRING", :base-type :type/Text}}} + :fields #{{:name "id", :database-type "INTEGER", :base-type :type/Integer, :database-position 0} + {:name "venue_name", :database-type "STRING", :base-type :type/Text, :database-position 1} + {:name "category_name", :database-type "STRING", :base-type :type/Text, :database-position 2}}} (driver/describe-table :bigquery (mt/db) {:name view-name})) "`describe-tables` should see the fields in the view") (sync/sync-database! (mt/db)) (testing "We should be able to run queries against the view (#3414)" - (is (= [[1 "Asian" "Red Medicine"] - [2 "Burger" "Stout Burgers & Beers"] - [3 "Burger" "The Apple Pan"]] + (is (= [[1 "Red Medicine" "Asian" ] + [2 "Stout Burgers & Beers" "Burger"] + [3 "The Apple Pan" "Burger"]] (mt/rows (qp/process-query {:database (mt/id) diff --git a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj index 14771da68179405d265df7121a478e0ef6cc1b77..f46c160a2f9036a7dbe56714a083f199e869dfef 100644 --- a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj +++ b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj @@ -1,5 +1,6 @@ (ns metabase.test.data.bigquery (:require [clojure.string :as str] + [flatland.ordered.map :as ordered-map] [java-time :as t] [medley.core :as m] [metabase @@ -18,10 +19,9 @@ [schema :as su]] [schema.core :as s]) (:import com.google.api.client.util.DateTime - com.google.api.services.bigquery.Bigquery - [com.google.api.services.bigquery.model Dataset DatasetReference QueryRequest QueryResponse - Table TableDataInsertAllRequest TableDataInsertAllRequest$Rows TableDataInsertAllResponse TableFieldSchema - TableReference TableRow TableSchema])) + [com.google.api.services.bigquery.model Dataset DatasetReference QueryRequest QueryResponse Table + TableDataInsertAllRequest TableDataInsertAllRequest$Rows TableDataInsertAllResponse TableFieldSchema + TableReference TableSchema])) (sql.tx/add-test-extensions! :bigquery) @@ -37,7 +37,11 @@ ;;; ----------------------------------------------- Connection Details ----------------------------------------------- -(def ^:private ^String normalize-name (comp #(str/replace % #"-" "_") name)) +(defn- normalize-name ^String [db-or-table identifier] + (let [s (str/replace (name identifier) "-" "_")] + (case db-or-table + :db (str "v2_" s) + :table s))) (def ^:private details (delay @@ -52,12 +56,12 @@ ^String [] (:project-id @details)) -(let [bigquery* (delay (#'bigquery/database->client {:details @details}))] - (defn- bigquery ^Bigquery [] - @bigquery*)) +(def ^:private ^{:arglists '(^com.google.api.services.bigquery.Bigquery [])} bigquery + "Get an instance of a `Bigquery` client." + (partial deref (delay (#'bigquery/database->client {:details @details})))) (defmethod tx/dbdef->connection-details :bigquery [_ _ {:keys [database-name]}] - (assoc @details :dataset-id (normalize-name database-name))) + (assoc @details :dataset-id (normalize-name :db database-name))) ;;; -------------------------------------------------- Loading Data -------------------------------------------------- @@ -72,13 +76,13 @@ (.setLocation "US") (.setDatasetReference (doto (DatasetReference.) (.setDatasetId dataset-id)))))) - (println (u/format-color 'blue "Created BigQuery dataset '%s'." dataset-id))) + (println (u/format-color 'blue "Created BigQuery dataset `%s.%s`." (project-id) dataset-id))) (defn- destroy-dataset! [^String dataset-id] {:pre [(seq dataset-id)]} (google/execute-no-auto-retry (doto (.delete (.datasets (bigquery)) (project-id) dataset-id) (.setDeleteContents true))) - (println (u/format-color 'red "Deleted BigQuery dataset '%s'." dataset-id))) + (println (u/format-color 'red "Deleted BigQuery dataset `%s.%s`." (project-id) dataset-id))) (defn execute! "Execute arbitrary (presumably DDL) SQL statements against the test project. Waits for statement to complete, throwing @@ -98,11 +102,23 @@ ;; characters long. (def ^:private ValidFieldName #"^[A-Za-z_]\w{0,127}$") +(s/defn ^:private delete-table! + [dataset-id :- su/NonBlankString, table-id :- su/NonBlankString] + (google/execute-no-auto-retry + (.delete + (.tables (bigquery)) + (project-id) + dataset-id + table-id)) + (println (u/format-color 'red "Deleted table `%s.%s.%s`" (project-id) dataset-id table-id))) + (s/defn ^:private create-table! [dataset-id :- su/NonBlankString - table-id :- su/NonBlankString, + table-id :- su/NonBlankString field-name->type :- {ValidFieldName (apply s/enum valid-field-types)}] - (google/execute + (u/ignore-exceptions + (delete-table! dataset-id table-id)) + (google/execute-no-auto-retry (.insert (.tables (bigquery)) (project-id) @@ -118,14 +134,21 @@ (.setMode "REQUIRED") (.setName (name field-name)) (.setType (name field-type)))))))))) - (println (u/format-color 'blue "Created BigQuery table '%s.%s'." dataset-id table-id))) + ;; now verify that the Table was created + (.tables (bigquery)) + (println (u/format-color 'blue "Created BigQuery table `%s.%s.%s`." (project-id) dataset-id table-id))) (defn- table-row-count ^Integer [^String dataset-id, ^String table-id] - (ffirst (:rows (#'bigquery/post-process-native - (google/execute - (.query (.jobs (bigquery)) (project-id) - (doto (QueryRequest.) - (.setQuery (format "SELECT COUNT(*) FROM [%s.%s]" dataset-id table-id))))))))) + (let [sql (format "SELECT count(*) FROM `%s.%s.%s`" (project-id) dataset-id table-id) + respond (fn [_ rows] + (ffirst rows)) + response (google/execute + (.query (.jobs (bigquery)) (project-id) + (doto (QueryRequest.) + (.setUseQueryCache false) + (.setUseLegacySql false) + (.setQuery sql))))] + (#'bigquery/post-process-native respond response))) (defprotocol ^:private Insertable (^:private ->insertable [this] @@ -138,48 +161,70 @@ Object (->insertable [this] this) + clojure.lang.Keyword + (->insertable [k] + (u/qualified-name k)) + java.time.temporal.Temporal - (->insertable [t] (str t)) + (->insertable [t] (u.date/format-sql t)) + ;; normalize to UTC. BigQuery normalizes it anyway and tends to complain when inserting values that have an offset + java.time.OffsetDateTime + (->insertable [t] + (->insertable (t/local-date-time (t/with-offset-same-instant t (t/zone-offset 0))))) + + ;; for whatever reason the `date time zone-id` syntax that works in SQL doesn't work when loading data java.time.ZonedDateTime - (->insertable [t] (->insertable (t/offset-date-time t))) + (->insertable [t] + (->insertable (t/offset-date-time t))) ;; normalize to UTC, since BigQuery doesn't support TIME WITH TIME ZONE java.time.OffsetTime - (->insertable [t] (->insertable (t/local-time (t/with-offset-same-instant t (t/zone-offset 0))))) + (->insertable [t] + (u.date/format-sql (t/local-time (t/with-offset-same-instant t (t/zone-offset 0))))) - ;; Convert the HoneySQL form we normally use to wrap a `Timestamp` to a plain literal string + ;; Convert the HoneySQL `timestamp(...)` form we sometimes use to wrap a `Timestamp` to a plain literal string honeysql.types.SqlCall - (->insertable [{[{s :literal}] :args}] + (->insertable [{[{s :literal}] :args, fn-name :name}] + (assert (= (name fn-name) "timestamp")) (->insertable (u.date/parse (str/replace s #"'" ""))))) +(defn- ->json [row-map] + (into {} (for [[k v] row-map] + [(name k) (->insertable v)]))) + +(defn- row->request-row ^TableDataInsertAllRequest$Rows [row-map] + (doto (TableDataInsertAllRequest$Rows.) + (.setJson (->json row-map)) + (.setInsertId (str (get row-map :id))))) + +(defn- rows->request ^TableDataInsertAllRequest [row-maps] + (doto (TableDataInsertAllRequest.) + (.setRows (into (list) (map row->request-row row-maps))) + (.setIgnoreUnknownValues false) + (.setSkipInvalidRows false))) + (defn- insert-data! [^String dataset-id, ^String table-id, row-maps] {:pre [(seq dataset-id) (seq table-id) (sequential? row-maps) (seq row-maps) (every? map? row-maps)]} - (let [rows - (for [row-map row-maps] - (let [data (TableRow.)] - (doseq [[k v] row-map - :let [v (->insertable v)]] - (.set data (name k) v)) - (doto (TableDataInsertAllRequest$Rows.) - (.setJson data)))) - - request - (.insertAll - (.tabledata (bigquery)) (project-id) dataset-id table-id - (doto (TableDataInsertAllRequest.) - (.setRows rows))) - - ^TableDataInsertAllResponse response (google/execute request)] + (let [rows (rows->request row-maps) + request (.insertAll + (.tabledata (bigquery)) + (project-id) dataset-id table-id + rows) + response ^TableDataInsertAllResponse (google/execute-no-auto-retry request)] + (println (u/format-color 'blue "Sent request to insert %d rows into `%s.%s.%s`" + (count (.getRows rows)) + (project-id) dataset-id table-id)) (when (seq (.getInsertErrors response)) (println "Error inserting rows:" (u/pprint-to-str (seq (.getInsertErrors response)))) (throw (ex-info "Error inserting rows" - {:errors (seq (.getInsertErrors response)) - :metabase.util/no-auto-retry? true - :rows row-maps}))) + {:errors (seq (.getInsertErrors response)) + :metabase.util/no-auto-retry? true + :rows row-maps + :data (.getRows rows)}))) ;; Wait up to 30 seconds for all the rows to be loaded and become available by BigQuery (let [expected-row-count (count row-maps)] - (loop [seconds-to-wait-for-load 30] + (loop [seconds-to-wait-for-load 10 #_30] ; NOCOMMIT (let [actual-row-count (table-row-count dataset-id table-id)] (cond (= expected-row-count actual-row-count) @@ -190,8 +235,8 @@ (recur (dec seconds-to-wait-for-load))) :else - (let [error-message (format "Failed to load table data for %s.%s: expected %d rows, loaded %d" - dataset-id table-id expected-row-count actual-row-count)] + (let [error-message (format "Failed to load table data for `%s.%s.%s`: expected %d rows, loaded %d" + (project-id) dataset-id table-id expected-row-count actual-row-count)] (println (u/format-color 'red error-message)) (throw (ex-info error-message {:metabase.util/no-auto-retry? true, :response response}))))))))) @@ -214,12 +259,14 @@ "Convert `field-definitions` to a format appropriate for passing to `create-table!`." [field-definitions] (into - {"id" :INTEGER} - (for [{:keys [field-name base-type]} field-definitions] - {field-name (or (base-type->bigquery-type base-type) - (let [message (format "Don't know what BigQuery type to use for base type: %s" base-type)] - (println (u/format-color 'red message)) - (throw (ex-info message {:metabase.util/no-auto-retry? true}))))}))) + (ordered-map/ordered-map) + (cons + ["id" :INTEGER] + (for [{:keys [field-name base-type]} field-definitions] + [field-name (or (base-type->bigquery-type base-type) + (let [message (format "Don't know what BigQuery type to use for base type: %s" base-type)] + (println (u/format-color 'red message)) + (throw (ex-info message {:metabase.util/no-auto-retry? true}))))])))) (defn- tabledef->prepared-rows "Convert `table-definition` to a format approprate for passing to `insert-data!`." @@ -231,10 +278,20 @@ :id (inc i))))) (defn- load-tabledef! [dataset-name {:keys [table-name field-definitions], :as tabledef}] - (let [table-name (normalize-name table-name)] + (let [table-name (normalize-name :table table-name)] (create-table! dataset-name table-name (fielddefs->field-name->base-type field-definitions)) - (insert-data! dataset-name table-name (tabledef->prepared-rows tabledef)))) - + ;; retry the `insert-data!` step up to 5 times because it seens to fail silently a lot. Since each row is given a + ;; unique key it shouldn't result in duplicates. + (loop [num-retries 5] + (let [^Throwable e (try + (insert-data! dataset-name table-name (tabledef->prepared-rows tabledef)) + nil + (catch Throwable e + e))] + (when e + (if (pos? num-retries) + (recur (dec num-retries)) + (throw e))))))) (defn- existing-dataset-names "Fetch a list of *all* dataset names that currently exist in the BQ test project." @@ -242,8 +299,13 @@ (for [dataset (get (google/execute (doto (.list (.datasets (bigquery)) (project-id)) ;; Long/MAX_VALUE barfs but it has to be a Long (.setMaxResults (long Integer/MAX_VALUE)))) - "datasets")] - (get-in dataset ["datasetReference" "datasetId"]))) + "datasets") + :let [dataset-name (get-in dataset ["datasetReference" "datasetId"])] + ;; don't consider that checkins_interval_ datasets created in + ;; `metabase.query-processor-test.date-bucketing-test` to be already created, since those test things relative + ;; to the current moment in time and thus need to be recreated before running the tests. + :when (not (str/includes? dataset-name "checkins_interval_"))] + dataset-name)) ;; keep track of databases we haven't created yet (def ^:private existing-datasets @@ -256,29 +318,37 @@ (reset! existing-datasets (set (existing-dataset-names))) (println "These BigQuery datasets have already been loaded:\n" (u/pprint-to-str (sort @existing-datasets)))) ;; now check and see if we need to create the requested one - (let [database-name (normalize-name database-name)] + (let [database-name (normalize-name :db database-name)] (when-not (contains? @existing-datasets database-name) (try + (u/ignore-exceptions + (destroy-dataset! database-name)) (u/auto-retry 2 ;; if the dataset failed to load successfully last time around, destroy whatever was loaded so we start ;; again from a blank slate (u/ignore-exceptions (destroy-dataset! database-name)) (create-dataset! database-name) - ;; do this in parallel because otherwise it can literally take an hour to load something like - ;; fifty_one_different_tables - (u/pdoseq [tabledef table-definitions] + ;; now create tables and load data. + (doseq [tabledef table-definitions] (load-tabledef! database-name tabledef)) (swap! existing-datasets conj database-name) - (println (u/format-color 'green "[OK]"))) + (println (u/format-color 'green "Successfully created %s." (pr-str database-name)))) ;; if creating the dataset ultimately fails to complete, then delete it so it will hopefully work next time ;; around (catch Throwable e - (println (u/format-color 'red "Failed to load BigQuery dataset '%s'." database-name)) + (println (u/format-color 'red "Failed to load BigQuery dataset %s." (pr-str database-name))) (u/ignore-exceptions (destroy-dataset! database-name)) (throw e)))))) +(defmethod tx/destroy-db! :bigquery + [_ {:keys [database-name]}] + (u/ignore-exceptions + (destroy-dataset! database-name)) + (when (seq @existing-datasets) + (swap! existing-datasets disj database-name))) + (defmethod tx/aggregate-column-info :bigquery ([driver aggregation-type] (merge diff --git a/modules/drivers/druid/src/metabase/driver/druid.clj b/modules/drivers/druid/src/metabase/driver/druid.clj index c8c6cc4804fc95bc69ae27584658c5de9090ecda..84467dfdfa1048ff81357d496998dbbfc74756c5 100644 --- a/modules/drivers/druid/src/metabase/driver/druid.clj +++ b/modules/drivers/druid/src/metabase/driver/druid.clj @@ -32,7 +32,8 @@ (defmethod driver/execute-reducible-query :druid [_ query context respond] - (execute/execute-reducible-query (partial client/do-query-with-cancellation (context/canceled-chan context)) query respond)) + (execute/execute-reducible-query (partial client/do-query-with-cancellation (context/canceled-chan context)) + query respond)) (doseq [[feature supported?] {:set-timezone true :expression-aggregations true}] diff --git a/modules/drivers/druid/src/metabase/driver/druid/sync.clj b/modules/drivers/druid/src/metabase/driver/druid/sync.clj index 4b13f76e28b12f2c9b5554c1a20c9aa38b998634..9f03b0f31d1c324f9efb4dfc373c373ac9996b2b 100644 --- a/modules/drivers/druid/src/metabase/driver/druid/sync.clj +++ b/modules/drivers/druid/src/metabase/driver/druid/sync.clj @@ -1,5 +1,6 @@ (ns metabase.driver.druid.sync - (:require [metabase.driver.druid.client :as client] + (:require [medley.core :as m] + [metabase.driver.druid.client :as client] [metabase.util.ssh :as ssh])) (defn- do-segment-metadata-query [details datasource] @@ -30,17 +31,19 @@ :name (:name table) :fields (set (cons ;; every Druid table is an event stream w/ a timestamp field - {:name "timestamp" - :database-type "timestamp" - :base-type :type/Instant - :pk? false} - (for [[field-name {field-type :type}] (dissoc columns :__time) - :let [metric? (contains? metric-column-names field-name)]] - {:name (name field-name) - :base-type (druid-type->base-type field-type) - :database-type (if metric? - (format "%s [metric]" field-type) - field-type)})))}))) + {:name "timestamp" + :database-type "timestamp" + :base-type :type/Instant + :pk? false + :database-position 0} + (for [[idx [field-name {field-type :type}]] (m/indexed (dissoc columns :__time)) + :let [metric? (contains? metric-column-names field-name)]] + {:name (name field-name) + :base-type (druid-type->base-type field-type) + :database-type (if metric? + (format "%s [metric]" field-type) + field-type) + :database-position (inc idx)})))}))) (defn describe-database "Impl of `driver/describe-database` for Druid." diff --git a/modules/drivers/druid/test/metabase/driver/druid/execute_test.clj b/modules/drivers/druid/test/metabase/driver/druid/execute_test.clj index fc2e7fb240f363433a3d2c05bfc656d732ab39be..a2448c3b5627a2b4ddc092752f0aaed526d84520 100644 --- a/modules/drivers/druid/test/metabase/driver/druid/execute_test.clj +++ b/modules/drivers/druid/test/metabase/driver/druid/execute_test.clj @@ -18,28 +18,28 @@ (assert (= (:status results) :completed) (u/pprint-to-str 'red results)) (testing "cols" - (is (= ["id" - "count" - "timestamp" - "user_last_login" - "user_name" - "venue_category_name" - "venue_latitude" - "venue_longitude" - "venue_name" - "venue_price"] + (is (= ["timestamp" + "venue_name" + "venue_longitude" + "venue_latitude" + "venue_price" + "venue_category_name" + "id" + "count" + "user_name" + "user_last_login"] (->> results :data :cols (map :name))))) (testing "rows" - (is (= [["931" - 1 - "2013-01-03T08:00:00Z" - "2014-01-01T08:30:00.000Z" - "Simcha Yan" - "Thai" - "34.094" - "-118.344" - "Kinaree Thai Bistro" - "1"]] + (is (= [["2013-01-03T08:00:00Z" + "Kinaree Thai Bistro" + "-118.344" + "34.094" + "1" + "Thai" + "931" + 1 + "Simcha Yan" + "2014-01-01T08:30:00.000Z"]] (-> results :data :rows))))))))) (deftest post-process-select-query-test diff --git a/modules/drivers/druid/test/metabase/driver/druid/sync_test.clj b/modules/drivers/druid/test/metabase/driver/druid/sync_test.clj index 9c2c94ee79d13fdf4cc065cb95b20b989f9f3e27..2c734d5a549f9a6942658626378a7e8ade3651f5 100644 --- a/modules/drivers/druid/test/metabase/driver/druid/sync_test.clj +++ b/modules/drivers/druid/test/metabase/driver/druid/sync_test.clj @@ -15,15 +15,15 @@ (testing "describe-table" (is (= {:schema nil :name "checkins" - :fields #{{:name "count", :base-type :type/Integer, :database-type "LONG [metric]"} - {:name "id", :base-type :type/Text, :database-type "STRING"} - {:name "timestamp", :base-type :type/Instant, :database-type "timestamp", :pk? false} - {:name "user_last_login", :base-type :type/Text, :database-type "STRING"} - {:name "user_name", :base-type :type/Text, :database-type "STRING"} - {:name "user_password", :base-type :type/Text, :database-type "STRING"} - {:name "venue_category_name", :base-type :type/Text, :database-type "STRING"} - {:name "venue_latitude", :base-type :type/Text, :database-type "STRING"} - {:name "venue_longitude", :base-type :type/Text, :database-type "STRING"} - {:name "venue_name", :base-type :type/Text, :database-type "STRING"} - {:name "venue_price", :base-type :type/Text, :database-type "STRING"}}} + :fields #{{:name "timestamp", :base-type :type/Instant, :database-type "timestamp", :database-position 0, :pk? false} + {:name "venue_name", :base-type :type/Text, :database-type "STRING", :database-position 1} + {:name "user_password", :base-type :type/Text, :database-type "STRING", :database-position 2} + {:name "venue_longitude", :base-type :type/Text, :database-type "STRING", :database-position 3} + {:name "venue_latitude", :base-type :type/Text, :database-type "STRING", :database-position 4} + {:name "venue_price", :base-type :type/Text, :database-type "STRING", :database-position 5} + {:name "venue_category_name", :base-type :type/Text, :database-type "STRING", :database-position 6} + {:name "id", :base-type :type/Text, :database-type "STRING", :database-position 7} + {:name "count", :base-type :type/Integer, :database-type "LONG [metric]", :database-position 8} + {:name "user_name", :base-type :type/Text, :database-type "STRING", :database-position 9} + {:name "user_last_login", :base-type :type/Text, :database-type "STRING", :database-position 10}}} (driver/describe-table :druid (mt/db) {:name "checkins"}))))))) diff --git a/modules/drivers/druid/test/metabase/test/data/druid.clj b/modules/drivers/druid/test/metabase/test/data/druid.clj index 2cf1ce52bbfd913ca866c9af9bb2ad3ce9f015ed..d2cb75db697f045b6c8ee37354879ed7fe67a8de 100644 --- a/modules/drivers/druid/test/metabase/test/data/druid.clj +++ b/modules/drivers/druid/test/metabase/test/data/druid.clj @@ -1,5 +1,7 @@ (ns metabase.test.data.druid - (:require [metabase.test.data.interface :as tx])) + (:require [metabase.test.data + [impl :as tx.impl] + [interface :as tx]])) (tx/add-test-extensions! :druid) @@ -8,5 +10,16 @@ {:host (tx/db-test-env-var-or-throw :druid :host) :port (Integer/parseInt (tx/db-test-env-var-or-throw :druid :port))}) -(defmethod tx/create-db! :druid [& _] +(defmethod tx/create-db! :druid + [& _] + nil) + +(defmethod tx/destroy-db! :druid + [& _] + nil) + +;; no-op -- because the names of the columns actually loaded by Druid differ from ones in the database definition, the +;; default impl will fail. TODO -- we should write an implementation that works for Druid +(defmethod tx.impl/verify-data-loaded-correctly :druid + [_ _ _] nil) diff --git a/modules/drivers/google/src/metabase/driver/google.clj b/modules/drivers/google/src/metabase/driver/google.clj index b3256f307110c98d7583f54f3c819be7b77cbc1e..1e46a1b74e449e20e389eb4eaa0947f26e95f4e7 100644 --- a/modules/drivers/google/src/metabase/driver/google.clj +++ b/modules/drivers/google/src/metabase/driver/google.clj @@ -35,12 +35,13 @@ "Execute `request`, and catch any `GoogleJsonResponseException` is throws, converting them to `ExceptionInfo` and rethrowing them." [^AbstractGoogleClientRequest request] - (try (.execute request) - (catch GoogleJsonResponseException e - (let [^GoogleJsonError error (.getDetails e)] - (throw (ex-info (or (.getMessage error) - (.getStatusMessage e)) - (into {} error))))))) + (try + (.execute request) + (catch GoogleJsonResponseException e + (let [^GoogleJsonError error (.getDetails e)] + (throw (ex-info (or (.getMessage error) + (.getStatusMessage e)) + (into {} error))))))) (defn execute "Execute `request`, and catch any `GoogleJsonResponseException` is throws, converting them to `ExceptionInfo` and diff --git a/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics.clj b/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics.clj index 9228a093103c07c3974f53e389f01ecd116a26c1..9239603cffb278112681cc010fa6e86bb8c7f7bd 100644 --- a/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics.clj +++ b/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics.clj @@ -1,6 +1,7 @@ (ns metabase.driver.googleanalytics (:require [cheshire.core :as json] [clojure.string :as str] + [medley.core :as m] [metabase [driver :as driver] [util :as u]] @@ -55,13 +56,14 @@ ;;; ------------------------------------------------- describe-table ------------------------------------------------- (defn- describe-columns [database] - (set (for [^Column column (metadata/columns database) + (set (for [[idx ^Column column] (m/indexed (metadata/columns database)) :let [ga-type (metadata/column-attribute column :dataType)]] - {:name (.getId column) - :base-type (if (= (.getId column) "ga:date") - :type/Date - (execute/ga-type->base-type ga-type)) - :database-type ga-type}))) + {:name (.getId column) + :base-type (if (= (.getId column) "ga:date") + :type/Date + (execute/ga-type->base-type ga-type)) + :database-type ga-type + :database-position idx}))) (defmethod driver/describe-table :googleanalytics [_ database table] diff --git a/modules/drivers/mongo/src/metabase/driver/mongo.clj b/modules/drivers/mongo/src/metabase/driver/mongo.clj index 44e5599aac18844f1b44ef2730ab95e3bf89faa7..45596769f605598a38b5ac50c8f18bd64a455e28 100644 --- a/modules/drivers/mongo/src/metabase/driver/mongo.clj +++ b/modules/drivers/mongo/src/metabase/driver/mongo.clj @@ -153,19 +153,28 @@ :type/MongoBSONID (driver.common/class->base-type klass))) -(defn- describe-table-field [field-kw field-info] - (let [most-common-object-type (most-common-object-type (vec (:types field-info)))] - (cond-> {:name (name field-kw) - :database-type (some-> most-common-object-type .getName) - :base-type (class->base-type most-common-object-type)} - (= :_id field-kw) (assoc :pk? true) - (:special-types field-info) (assoc :special-type (->> (vec (:special-types field-info)) - (filter #(some? (first %))) - (sort-by second) - last - first)) - (:nested-fields field-info) (assoc :nested-fields (set (for [field (keys (:nested-fields field-info))] - (describe-table-field field (field (:nested-fields field-info))))))))) +(defn- describe-table-field [field-kw field-info idx] + (let [most-common-object-type (most-common-object-type (vec (:types field-info))) + [nested-fields idx-next] + (reduce + (fn [[nested-fields idx] nested-field] + (let [[nested-field idx-next] (describe-table-field nested-field + (nested-field (:nested-fields field-info)) + idx)] + [(conj nested-fields nested-field) idx-next])) + [#{} (inc idx)] + (keys (:nested-fields field-info)))] + [(cond-> {:name (name field-kw) + :database-type (some-> most-common-object-type .getName) + :base-type (class->base-type most-common-object-type) + :database-position idx} + (= :_id field-kw) (assoc :pk? true) + (:special-types field-info) (assoc :special-type (->> (:special-types field-info) + (filterv #(some? (first %))) + (sort-by second) + last + first)) + (:nested-fields field-info) (assoc :nested-fields nested-fields)) idx-next])) (defmethod driver/describe-database :mongo [_ database] @@ -199,8 +208,12 @@ (let [column-info (table-sample-column-info conn table)] {:schema nil :name (:name table) - :fields (set (for [[field info] column-info] - (describe-table-field field info)))}))) + :fields (first + (reduce (fn [[fields idx] [field info]] + (let [[described-field new-idx] (describe-table-field field info idx)] + [(conj fields described-field) new-idx])) + [#{} 0] + column-info))}))) (doseq [feature [:basic-aggregations :nested-fields diff --git a/modules/drivers/mongo/test/metabase/driver/mongo/parameters_test.clj b/modules/drivers/mongo/test/metabase/driver/mongo/parameters_test.clj index d89de4af5fb7ba7e44c83b7e5562a288fd0db8f5..0ad8c95c413c7b65c4a1727f342426feae1df405 100644 --- a/modules/drivers/mongo/test/metabase/driver/mongo/parameters_test.clj +++ b/modules/drivers/mongo/test/metabase/driver/mongo/parameters_test.clj @@ -167,9 +167,9 @@ (deftest e2e-field-filter-test (mt/test-driver :mongo (testing "date ranges" - (is (= [[295 7 97 "2014-03-01T00:00:00Z"] - [642 8 9 "2014-03-02T00:00:00Z"] - [775 4 13 "2014-03-01T00:00:00Z"]] + (is (= [[295 "2014-03-01T00:00:00Z" 7 97] + [642 "2014-03-02T00:00:00Z" 8 9] + [775 "2014-03-01T00:00:00Z" 4 13]] (mt/rows (qp/process-query (mt/query checkins @@ -204,7 +204,7 @@ :target [:dimension [:template-tag "id"]] :value "1,2,3"}]})))))) (testing "param not supplied" - (is (= [[1 5 12 "2014-04-07T00:00:00Z"]] + (is (= [[1 "2014-04-07T00:00:00Z" 5 12]] (mt/rows (qp/process-query (mt/query checkins diff --git a/modules/drivers/mongo/test/metabase/driver/mongo_test.clj b/modules/drivers/mongo/test/metabase/driver/mongo_test.clj index abfbbda8abd9a632bf30064f0e6b6c7cd510ec96..c232fcd091bedaf90e7c03a1d51f52c21195985f 100644 --- a/modules/drivers/mongo/test/metabase/driver/mongo_test.clj +++ b/modules/drivers/mongo/test/metabase/driver/mongo_test.clj @@ -105,23 +105,29 @@ :name "venues" :fields #{{:name "name" :database-type "java.lang.String" - :base-type :type/Text} + :base-type :type/Text + :database-position 1} {:name "latitude" :database-type "java.lang.Double" - :base-type :type/Float} + :base-type :type/Float + :database-position 3} {:name "longitude" :database-type "java.lang.Double" - :base-type :type/Float} + :base-type :type/Float + :database-position 4} {:name "price" :database-type "java.lang.Long" - :base-type :type/Integer} + :base-type :type/Integer + :database-position 5} {:name "category_id" :database-type "java.lang.Long" - :base-type :type/Integer} + :base-type :type/Integer + :database-position 2} {:name "_id" :database-type "java.lang.Long" :base-type :type/Integer - :pk? true}}} + :pk? true + :database-position 0}}} (driver/describe-table :mongo (mt/db) (Table (mt/id :venues))))))) (deftest nested-columns-test diff --git a/modules/drivers/mongo/test/metabase/test/data/mongo.clj b/modules/drivers/mongo/test/metabase/test/data/mongo.clj index 8ad6028a0c2048e1e286fdedd5e656d1dc7e3031..dfd86d443e42c322cf731a3312e07a052388f221 100644 --- a/modules/drivers/mongo/test/metabase/test/data/mongo.clj +++ b/modules/drivers/mongo/test/metabase/test/data/mongo.clj @@ -37,11 +37,15 @@ (doseq [[i row] (map-indexed (partial vector) rows)] (try ;; Insert each row - (mc/insert mongo-db (name table-name) (assoc (zipmap field-names row) - :_id (inc i))) + (mc/insert mongo-db (name table-name) (into {:_id (inc i)} + (zipmap field-names row))) ;; If row already exists then nothing to do (catch com.mongodb.MongoException _))))))) +(defmethod tx/destroy-db! :mongo + [driver dbdef] + (destroy-db! driver dbdef)) + (defmethod tx/format-name :mongo [_ table-or-field-name] (if (= table-or-field-name "id") diff --git a/modules/drivers/presto/src/metabase/driver/presto.clj b/modules/drivers/presto/src/metabase/driver/presto.clj index bffe39dcf67d2251e8388ca96fc9fd752e7e7f7e..9a8a8d54363b7d7b2cde6790fe2c7c5c34e45b4c 100644 --- a/modules/drivers/presto/src/metabase/driver/presto.clj +++ b/modules/drivers/presto/src/metabase/driver/presto.clj @@ -11,6 +11,7 @@ [core :as hsql] [helpers :as h]] [java-time :as t] + [medley.core :as m] [metabase [driver :as driver] [util :as u]] @@ -245,13 +246,15 @@ (defmethod driver/describe-table :presto [driver {{:keys [catalog] :as details} :details} {schema :schema, table-name :name}] (let [sql (str "DESCRIBE " (sql.u/quote-name driver :table catalog schema table-name)) + _ (println "sql:" sql) ; NOCOMMIT {:keys [rows]} (execute-presto-query-for-sync details sql)] {:schema schema :name table-name - :fields (set (for [[name type] rows] - {:name name - :database-type type - :base-type (presto-type->base-type type)}))})) + :fields (set (for [[idx [name type]] (m/indexed rows)] + {:name name + :database-type type + :base-type (presto-type->base-type type) + :database-position idx}))})) (defmethod sql.qp/->honeysql [:presto Boolean] [_ bool] diff --git a/modules/drivers/presto/test/metabase/driver/presto_test.clj b/modules/drivers/presto/test/metabase/driver/presto_test.clj index fac8995028bfdd9d7131a2523cbea2016526f5a2..a43d4d4201b88c3c759fb865fea6e4376a5ab084 100644 --- a/modules/drivers/presto/test/metabase/driver/presto_test.clj +++ b/modules/drivers/presto/test/metabase/driver/presto_test.clj @@ -93,22 +93,28 @@ :schema "default" :fields #{{:name "name", :database-type "varchar(255)" - :base-type :type/Text} + :base-type :type/Text + :database-position 1} {:name "latitude" :database-type "double" - :base-type :type/Float} + :base-type :type/Float + :database-position 3} {:name "longitude" :database-type "double" - :base-type :type/Float} + :base-type :type/Float + :database-position 4} {:name "price" :database-type "integer" - :base-type :type/Integer} + :base-type :type/Integer + :database-position 5} {:name "category_id" :database-type "integer" - :base-type :type/Integer} + :base-type :type/Integer + :database-position 2} {:name "id" :database-type "integer" - :base-type :type/Integer}}} + :base-type :type/Integer + :database-position 0}}} (driver/describe-table :presto (mt/db) (db/select-one 'Table :id (mt/id :venues))))))) (deftest table-rows-sample-test diff --git a/modules/drivers/presto/test/metabase/test/data/presto.clj b/modules/drivers/presto/test/metabase/test/data/presto.clj index adaee0d63e6e5a442eaf127528422b7fbfad71ba..f3deab9e15f2d096bc6ac9711921c1cc1c8ee803 100644 --- a/modules/drivers/presto/test/metabase/test/data/presto.clj +++ b/modules/drivers/presto/test/metabase/test/data/presto.clj @@ -61,7 +61,7 @@ (defmethod sql.tx/create-table-sql :presto [driver {:keys [database-name]} {:keys [table-name], :as tabledef}] - (let [field-definitions (conj (:field-definitions tabledef) {:field-name "id", :base-type :type/Integer}) + (let [field-definitions (cons {:field-name "id", :base-type :type/Integer} (:field-definitions tabledef)) dummy-values (map (comp field-base-type->dummy-value :base-type) field-definitions) columns (map :field-name field-definitions)] ;; Presto won't let us use the `CREATE TABLE (...)` form, but we can still do it creatively if we select the right @@ -77,14 +77,14 @@ (str "DROP TABLE IF EXISTS " (sql.tx/qualify-and-quote driver database-name table-name))) (defn- insert-sql [driver {:keys [database-name]} {:keys [table-name], :as tabledef} rows] - (let [field-definitions (conj (:field-definitions tabledef) {:field-name "id"}) + (let [field-definitions (cons {:field-name "id"} (:field-definitions tabledef)) columns (map (comp keyword :field-name) field-definitions) [query & params] (-> (apply h/columns columns) (h/insert-into (apply hsql/qualify (sql.tx/qualified-name-components driver database-name table-name))) (h/values rows) (hsql/format :allow-dashed-names? true, :quoting :ansi))] - (log/trace "Inserting Presto rows") + (log/tracef "Inserting Presto rows") (doseq [row rows] (log/trace (str/join ", " (map #(format "^%s %s" (.getName (class %)) (pr-str %)) row)))) (if (nil? params) @@ -98,7 +98,7 @@ (doseq [tabledef table-definitions :let [rows (:rows tabledef) ;; generate an ID for each row because we don't have auto increments - keyed-rows (map-indexed (fn [i row] (conj row (inc i))) rows) + keyed-rows (map-indexed (fn [i row] (cons (inc i) row)) rows) ;; make 100 rows batches since we have to inline everything batches (partition 100 100 nil keyed-rows)]] (when-not skip-drop-db? @@ -107,6 +107,15 @@ (doseq [batch batches] (execute! (insert-sql driver dbdef tabledef batch)))))) +(defmethod tx/destroy-db! :presto + [driver {:keys [database-name table-definitions], :as dbdef}] + (let [details (tx/dbdef->connection-details driver :db dbdef) + execute! (partial #'presto/execute-presto-query-for-sync details)] + (doseq [{:keys [table-name], :as tabledef} table-definitions] + (println (format "[Presto] destroying %s.%s" (pr-str database-name) (pr-str table-name))) + (execute! (sql.tx/drop-table-if-exists-sql driver dbdef tabledef)) + (println "[Presto] [ok]")))) + (defmethod tx/format-name :presto [_ s] (str/lower-case s)) diff --git a/modules/drivers/snowflake/project.clj b/modules/drivers/snowflake/project.clj index 275380228bd0f9259e1681f1f6fe6987267f0ba5..7f17ad2d990f08d6b1ccecc53208a25a17177ab8 100644 --- a/modules/drivers/snowflake/project.clj +++ b/modules/drivers/snowflake/project.clj @@ -1,8 +1,8 @@ -(defproject metabase/snowflake-driver "1.0.0-SNAPSHOT-3.12.5" +(defproject metabase/snowflake-driver "1.0.0-SNAPSHOT-3.12.7" :min-lein-version "2.5.0" :dependencies - [[net.snowflake/snowflake-jdbc "3.12.5"]] + [[net.snowflake/snowflake-jdbc "3.12.7"]] :profiles {:provided diff --git a/modules/drivers/snowflake/resources/metabase-plugin.yaml b/modules/drivers/snowflake/resources/metabase-plugin.yaml index e2a8a5e872b5e2f8052524cc6a264a4dcb4404e4..00639566aa8f6c97b19c76b41e055df344a19ba7 100644 --- a/modules/drivers/snowflake/resources/metabase-plugin.yaml +++ b/modules/drivers/snowflake/resources/metabase-plugin.yaml @@ -1,6 +1,6 @@ info: name: Metabase Snowflake Driver - version: 1.0.0-SNAPSHOT-3.12.5 + version: 1.0.0-SNAPSHOT-3.12.7 description: Allows Metabase to connect to Snowflake databases. driver: name: snowflake diff --git a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj index bd568540de5a9c860a7732e84d797eabdd6cf1d7..961adcd27577fb9d9f2b9be17372c9618365b602 100644 --- a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj +++ b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj @@ -219,8 +219,17 @@ (defmethod driver/describe-database :snowflake [driver database] - {:tables (jdbc/with-db-metadata [metadata (sql-jdbc.conn/db->pooled-connection-spec database)] - (sql-jdbc.sync/fast-active-tables driver metadata (db-name database)))}) + ;; using the JDBC `.getTables` method seems to be pretty buggy -- it works sometimes but other times randomly + ;; returns nothing + (let [db-name (db-name database) + excluded-schemas (set (sql-jdbc.sync/excluded-schemas driver))] + {:tables (set (for [table (jdbc/query + (sql-jdbc.conn/db->pooled-connection-spec database) + (format "SHOW TABLES IN DATABASE \"%s\"" db-name)) + :when (not (contains? excluded-schemas (:schema_name table)))] + {:name (:name table) + :schema (:schema_name table) + :description (not-empty (:comment table))}))})) (defmethod driver/describe-table :snowflake [driver database table] @@ -281,19 +290,21 @@ ;; Like Vertica, Snowflake doesn't seem to be able to return a LocalTime/OffsetTime like everyone else, but it can ;; return a String that we can parse -(defmethod sql-jdbc.execute/read-column [:snowflake Types/TIME] - [_ _ ^ResultSet rs _ ^Integer i] - (when-let [s (.getString rs i)] - (let [t (u.date/parse s)] - (log/tracef "(.getString rs %d) [TIME] -> %s -> %s" i s t) - t))) - -(defmethod sql-jdbc.execute/read-column [:snowflake Types/TIME_WITH_TIMEZONE] - [_ _ ^ResultSet rs _ ^Integer i] - (when-let [s (.getString rs i)] - (let [t (u.date/parse s)] - (log/tracef "(.getString rs %d) [TIME_WITH_TIMEZONE] -> %s -> %s" i s t) - t))) +(defmethod sql-jdbc.execute/read-column-thunk [:snowflake Types/TIME] + [_ ^ResultSet rs _ ^Integer i] + (fn [] + (when-let [s (.getString rs i)] + (let [t (u.date/parse s)] + (log/tracef "(.getString rs %d) [TIME] -> %s -> %s" i (pr-str s) (pr-str t)) + t)))) + +(defmethod sql-jdbc.execute/read-column-thunk [:snowflake Types/TIME_WITH_TIMEZONE] + [_ ^ResultSet rs _ ^Integer i] + (fn [] + (when-let [s (.getString rs i)] + (let [t (u.date/parse s)] + (log/tracef "(.getString rs %d) [TIME_WITH_TIMEZONE] -> %s -> %s" i (pr-str s) (pr-str t)) + t)))) ;; TODO  would it make more sense to use functions like `timestamp_tz_from_parts` directly instead of JDBC parameters? diff --git a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj index 22172a6086551edb5020e36179d3cccc3bbf26f7..a09ee6517d55d609023876b1311a546fde103a6f 100644 --- a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj +++ b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj @@ -1,5 +1,6 @@ (ns metabase.driver.snowflake-test (:require [clojure + [set :as set] [string :as str] [test :refer :all]] [metabase @@ -12,65 +13,74 @@ [sql :as sql.tx]] [metabase.test.data.sql.ddl :as ddl])) -;; make sure we didn't break the code that is used to generate DDL statements when we add new test datasets +;; (deftest ddl-statements-test - (is (= "DROP DATABASE IF EXISTS \"test-data\"; CREATE DATABASE \"test-data\";" - (sql.tx/create-db-sql :snowflake (mt/get-dataset-definition dataset-defs/test-data)))) - (is (= (map - #(str/replace % #"\s+" " ") - ["DROP TABLE IF EXISTS \"test-data\".\"PUBLIC\".\"users\";" - "CREATE TABLE \"test-data\".\"PUBLIC\".\"users\" (\"name\" TEXT, \"last_login\" TIMESTAMP_LTZ, \"password\" - TEXT, \"id\" INTEGER AUTOINCREMENT, PRIMARY KEY (\"id\")) ;" - "DROP TABLE IF EXISTS \"test-data\".\"PUBLIC\".\"categories\";" - "CREATE TABLE \"test-data\".\"PUBLIC\".\"categories\" (\"name\" TEXT, \"id\" INTEGER AUTOINCREMENT, PRIMARY - KEY (\"id\")) ;" - "DROP TABLE IF EXISTS \"test-data\".\"PUBLIC\".\"venues\";" - "CREATE TABLE \"test-data\".\"PUBLIC\".\"venues\" (\"name\" TEXT, \"latitude\" FLOAT, \"longitude\" FLOAT, - \"price\" INTEGER, \"category_id\" INTEGER, \"id\" INTEGER AUTOINCREMENT, PRIMARY KEY (\"id\")) ;" - "DROP TABLE IF EXISTS \"test-data\".\"PUBLIC\".\"checkins\";" - "CREATE TABLE \"test-data\".\"PUBLIC\".\"checkins\" (\"user_id\" INTEGER, \"venue_id\" INTEGER, \"date\" - DATE, \"id\" INTEGER AUTOINCREMENT, PRIMARY KEY (\"id\")) ;" - "ALTER TABLE \"test-data\".\"PUBLIC\".\"venues\" ADD CONSTRAINT \"tegory_id_categories_927642602\" FOREIGN - KEY (\"category_id\") REFERENCES \"test-data\".\"PUBLIC\".\"categories\" (\"id\");" - "ALTER TABLE \"test-data\".\"PUBLIC\".\"checkins\" ADD CONSTRAINT \"ckins_user_id_users_-815717481\" - FOREIGN KEY (\"user_id\") REFERENCES \"test-data\".\"PUBLIC\".\"users\" (\"id\");" - "ALTER TABLE \"test-data\".\"PUBLIC\".\"checkins\" ADD CONSTRAINT \"ns_venue_id_venues_-1854903846\" - FOREIGN KEY (\"venue_id\") REFERENCES \"test-data\".\"PUBLIC\".\"venues\" (\"id\");"]) - (ddl/create-db-ddl-statements :snowflake (mt/get-dataset-definition dataset-defs/test-data))))) + (testing "make sure we didn't break the code that is used to generate DDL statements when we add new test datasets" + (testing "Create DB DDL statements" + (is (= "DROP DATABASE IF EXISTS \"v2_test-data\"; CREATE DATABASE \"v2_test-data\";" + (sql.tx/create-db-sql :snowflake (mt/get-dataset-definition dataset-defs/test-data))))) + + (testing "Create Table DDL statements" + (is (= (map + #(str/replace % #"\s+" " ") + ["DROP TABLE IF EXISTS \"v2_test-data\".\"PUBLIC\".\"users\";" + "CREATE TABLE \"v2_test-data\".\"PUBLIC\".\"users\" (\"id\" INTEGER AUTOINCREMENT, \"name\" TEXT, + \"last_login\" TIMESTAMP_LTZ, \"password\" TEXT, PRIMARY KEY (\"id\")) ;" + "DROP TABLE IF EXISTS \"v2_test-data\".\"PUBLIC\".\"categories\";" + "CREATE TABLE \"v2_test-data\".\"PUBLIC\".\"categories\" (\"id\" INTEGER AUTOINCREMENT, \"name\" TEXT, + PRIMARY KEY (\"id\")) ;" + "DROP TABLE IF EXISTS \"v2_test-data\".\"PUBLIC\".\"venues\";" + "CREATE TABLE \"v2_test-data\".\"PUBLIC\".\"venues\" (\"id\" INTEGER AUTOINCREMENT, \"name\" TEXT, + \"category_id\" INTEGER, \"latitude\" FLOAT, \"longitude\" FLOAT, \"price\" INTEGER, PRIMARY KEY (\"id\")) ;" + "DROP TABLE IF EXISTS \"v2_test-data\".\"PUBLIC\".\"checkins\";" + "CREATE TABLE \"v2_test-data\".\"PUBLIC\".\"checkins\" (\"id\" INTEGER AUTOINCREMENT, \"date\" DATE, + \"user_id\" INTEGER, \"venue_id\" INTEGER, PRIMARY KEY (\"id\")) ;" + "ALTER TABLE \"v2_test-data\".\"PUBLIC\".\"venues\" ADD CONSTRAINT \"gory_id_categories_-1524018980\" + FOREIGN KEY (\"category_id\") REFERENCES \"v2_test-data\".\"PUBLIC\".\"categories\" (\"id\");" + "ALTER TABLE \"v2_test-data\".\"PUBLIC\".\"checkins\" ADD CONSTRAINT \"ckins_user_id_users_-230440067\" + FOREIGN KEY (\"user_id\") REFERENCES \"v2_test-data\".\"PUBLIC\".\"users\" (\"id\");" + "ALTER TABLE \"v2_test-data\".\"PUBLIC\".\"checkins\" ADD CONSTRAINT \"kins_venue_id_venues_621212269\" + FOREIGN KEY (\"venue_id\") REFERENCES \"v2_test-data\".\"PUBLIC\".\"venues\" (\"id\");"]) + (ddl/create-db-ddl-statements :snowflake (-> (mt/get-dataset-definition dataset-defs/test-data) + (update :database-name #(str "v2_" %))))))))) ;; TODO -- disabled because these are randomly failing, will figure out when I'm back from vacation. I think it's a ;; bug in the JDBC driver -- Cam -#_(deftest describe-database-test - (mt/test-driver :snowflake - (testing "describe-database" - (let [expected {:tables - #{{:name "users", :schema "PUBLIC", :description nil} - {:name "venues", :schema "PUBLIC", :description nil} - {:name "checkins", :schema "PUBLIC", :description nil} - {:name "categories", :schema "PUBLIC", :description nil}}}] - (testing "should work with normal details" - (is (= expected - (driver/describe-database :snowflake (mt/db))))) - (testing "should accept either `:db` or `:dbname` in the details, working around a bug with the original impl" - (is (= expected - (driver/describe-database :snowflake (update (mt/db) :details set/rename-keys {:db :dbname}))))) - (testing "should throw an Exception if details have neither `:db` nor `:dbname`" - (is (thrown? Exception - (driver/describe-database :snowflake (update (mt/db) :details set/rename-keys {:db :xyz}))))) - (testing "should use the NAME FROM DETAILS instead of the DB DISPLAY NAME to fetch metadata (#8864)" - (is (= expected - (driver/describe-database :snowflake (assoc (mt/db) :name "ABC"))))))))) +(deftest describe-database-test + (mt/test-driver :snowflake + (testing "describe-database" + (let [expected {:tables + #{{:name "users", :schema "PUBLIC", :description nil} + {:name "venues", :schema "PUBLIC", :description nil} + {:name "checkins", :schema "PUBLIC", :description nil} + {:name "categories", :schema "PUBLIC", :description nil}}}] + (testing "should work with normal details" + (is (= expected + (driver/describe-database :snowflake (mt/db))))) + (testing "should accept either `:db` or `:dbname` in the details, working around a bug with the original impl" + (is (= expected + (driver/describe-database :snowflake (update (mt/db) :details set/rename-keys {:db :dbname}))))) + (testing "should throw an Exception if details have neither `:db` nor `:dbname`" + (is (thrown? Exception + (driver/describe-database :snowflake (update (mt/db) :details set/rename-keys {:db :xyz}))))) + (testing "should use the NAME FROM DETAILS instead of the DB DISPLAY NAME to fetch metadata (#8864)" + (is (= expected + (driver/describe-database :snowflake (assoc (mt/db) :name "ABC"))))))))) (deftest describe-table-test (mt/test-driver :snowflake (testing "make sure describe-table uses the NAME FROM DETAILS too" (is (= {:name "categories" :schema "PUBLIC" - :fields #{{:name "id" - :database-type "NUMBER" - :base-type :type/Number - :pk? true} - {:name "name", :database-type "VARCHAR", :base-type :type/Text}}} + :fields #{{:name "id" + :database-type "NUMBER" + :base-type :type/Number + :pk? true + :database-position 0} + {:name "name" + :database-type "VARCHAR" + :base-type :type/Text + :database-position 1}}} (driver/describe-table :snowflake (assoc (mt/db) :name "ABC") (Table (mt/id :categories)))))))) (deftest describe-table-fks-test @@ -124,7 +134,7 @@ {:database (mt/id) :type :native :native {:query (str "SELECT {{filter_date}}, \"last_login\" " - "FROM \"test-data\".\"PUBLIC\".\"users\" " + "FROM \"v2_test-data\".\"PUBLIC\".\"users\" " "WHERE date_trunc('day', CAST(\"last_login\" AS timestamp))" " = date_trunc('day', CAST({{filter_date}} AS timestamp))") :template-tags {:filter_date {:name "filter_date" diff --git a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj index 70282ab36b227aab4d5a36f9701fbd949c149a40..edb20884a46a7b27beed37039c3327a4dbd48f24 100644 --- a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj +++ b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj @@ -31,6 +31,14 @@ :type/Time "TIME"}] (defmethod sql.tx/field-base-type->sql-type [:snowflake base-type] [_ _] sql-type)) +(defn- qualified-db-name + "Prepend `database-name` with a version number so we can create new versions without breaking existing tests." + [database-name] + ;; try not to qualify the database name twice! + (if (str/starts-with? database-name "v2_") + database-name + (str "v2_" database-name))) + (defmethod tx/dbdef->connection-details :snowflake [_ context {:keys [database-name]}] (merge @@ -43,7 +51,7 @@ ;; Snowflake JDBC driver ignores this, but we do use it in the `query-db-name` function in ;; `metabase.driver.snowflake` (when (= context :db) - {:db database-name}))) + {:db (qualified-db-name database-name)}))) ;; Snowflake requires you identify an object with db-name.schema-name.table-name (defmethod sql.tx/qualified-name-components :snowflake @@ -53,7 +61,7 @@ (defmethod sql.tx/create-db-sql :snowflake [driver {:keys [database-name]}] - (let [db (sql.tx/qualify-and-quote driver database-name)] + (let [db (sql.tx/qualify-and-quote driver (qualified-db-name database-name))] (format "DROP DATABASE IF EXISTS %s; CREATE DATABASE %s;" db db))) (defn- no-db-connection-spec @@ -76,26 +84,37 @@ @datasets) (defn- add-existing-dataset! [database-name] - (swap! datasets conj database-name))) + (swap! datasets conj database-name)) + + (defn- remove-existing-dataset! [database-name] + (swap! datasets disj database-name))) (defmethod tx/create-db! :snowflake - [driver {:keys [database-name] :as db-def} & options] - ;; ok, now check if already created. If already created, no-op - (when-not (contains? (existing-datasets) database-name) - ;; if not created, create the DB... - (try - ;; call the default impl for SQL JDBC drivers - (apply (get-method tx/create-db! :sql-jdbc/test-extensions) driver db-def options) - ;; and add it to the set of DBs that have been created - (add-existing-dataset! database-name) - ;; if creating the DB failed, DROP it so we don't get stuck with a DB full of bad data and skip trying to - ;; load it next time around - (catch Throwable e - (let [drop-db-sql (format "DROP DATABASE \"%s\";" database-name)] - (println "Creating DB failed:" e) - (println "Executing" drop-db-sql) - (jdbc/execute! (no-db-connection-spec) [drop-db-sql])) - (throw e))))) + [driver db-def & options] + (let [{:keys [database-name], :as db-def} (update db-def :database-name qualified-db-name)] + ;; ok, now check if already created. If already created, no-op + (when-not (contains? (existing-datasets) database-name) + (println (format "Creating new Snowflake database %s..." (pr-str database-name))) + ;; if not created, create the DB... + (try + ;; call the default impl for SQL JDBC drivers + (apply (get-method tx/create-db! :sql-jdbc/test-extensions) driver db-def options) + ;; and add it to the set of DBs that have been created + (add-existing-dataset! database-name) + ;; if creating the DB failed, DROP it so we don't get stuck with a DB full of bad data and skip trying to + ;; load it next time around + (catch Throwable e + (let [drop-db-sql (format "DROP DATABASE \"%s\";" database-name)] + (println "Creating DB failed:" e) + (println "[Snowflake]" drop-db-sql) + (jdbc/execute! (no-db-connection-spec) [drop-db-sql])) + (throw e)))))) + +(defmethod tx/destroy-db! :snowflake + [_ {:keys [database-name]}] + (let [database-name (qualified-db-name database-name)] + (jdbc/execute! (no-db-connection-spec) [(format "DROP DATABASE \"%s\";" database-name)]) + (remove-existing-dataset! database-name))) ;; For reasons I don't understand the Snowflake JDBC driver doesn't seem to work when trying to use parameterized ;; INSERT statements, even though the documentation suggests it should. Just go ahead and deparameterize all the diff --git a/modules/drivers/sparksql/src/metabase/driver/sparksql.clj b/modules/drivers/sparksql/src/metabase/driver/sparksql.clj index a6f9e27ed00b5debe82401c5740beaaff2f4432d..8056b71c564d43af7fa825705f683dfcb39a5bc4 100644 --- a/modules/drivers/sparksql/src/metabase/driver/sparksql.clj +++ b/modules/drivers/sparksql/src/metabase/driver/sparksql.clj @@ -6,6 +6,7 @@ [honeysql [core :as hsql] [helpers :as h]] + [medley.core :as m] [metabase.driver :as driver] [metabase.driver.hive-like :as hive-like] [metabase.driver.sql @@ -119,11 +120,12 @@ (dash-to-underscore schema) (dash-to-underscore table-name)))])] (set - (for [{col-name :col_name, data-type :data_type, :as result} results - :when (valid-describe-table-row? result)] - {:name col-name - :database-type data-type - :base-type (sql-jdbc.sync/database-type->base-type :hive-like (keyword data-type))}))))}) + (for [[idx {col-name :col_name, data-type :data_type, :as result}] (m/indexed results) + :when (valid-describe-table-row? result)] + {:name col-name + :database-type data-type + :base-type (sql-jdbc.sync/database-type->base-type :hive-like (keyword data-type)) + :database-position idx}))))}) ;; bound variables are not supported in Spark SQL (maybe not Hive either, haven't checked) (defmethod driver/execute-reducible-query :sparksql diff --git a/modules/drivers/sparksql/test/metabase/test/data/sparksql.clj b/modules/drivers/sparksql/test/metabase/test/data/sparksql.clj index fd8450ee786cb4c0964596ae4b70dffe3df7adcc..e09264601bd324369045a87b386fad74d505d43e 100644 --- a/modules/drivers/sparksql/test/metabase/test/data/sparksql.clj +++ b/modules/drivers/sparksql/test/metabase/test/data/sparksql.clj @@ -93,17 +93,16 @@ [driver {:keys [database-name], :as dbdef} {:keys [table-name field-definitions]}] (let [quote-name #(sql.u/quote-name driver :field (tx/format-name driver %)) pk-field-name (quote-name (sql.tx/pk-field-name driver))] - (format "CREATE TABLE %s (%s, %s %s)" + (format "CREATE TABLE %s (%s %s, %s)" (sql.tx/qualify-and-quote driver database-name table-name) + pk-field-name (sql.tx/pk-sql-type driver) (->> field-definitions (map (fn [{:keys [field-name base-type]}] (format "%s %s" (quote-name field-name) (if (map? base-type) (:native base-type) (sql.tx/field-base-type->sql-type driver base-type))))) (interpose ", ") - (apply str)) - pk-field-name (sql.tx/pk-sql-type driver) - pk-field-name))) + (apply str))))) (defmethod sql.tx/drop-table-if-exists-sql :sparksql [driver {:keys [database-name]} {:keys [table-name]}] diff --git a/modules/drivers/sqlserver/test/metabase/test/data/sqlserver.clj b/modules/drivers/sqlserver/test/metabase/test/data/sqlserver.clj index bc9a7086d7ac9c4b1d79c9aec1d729375403179e..9029544cd3fc6f006f724a5df88744bb9821c96f 100644 --- a/modules/drivers/sqlserver/test/metabase/test/data/sqlserver.clj +++ b/modules/drivers/sqlserver/test/metabase/test/data/sqlserver.clj @@ -61,3 +61,16 @@ ([_ db-name table-name field-name] [db-name "dbo" table-name field-name])) (defmethod sql.tx/pk-sql-type :sqlserver [_] "INT IDENTITY(1,1)") + +(defmethod tx/aggregate-column-info :sqlserver + ([driver ag-type] + (merge + ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type) + (when (#{:count :cum-count} ag-type) + {:base_type :type/Integer}))) + + ([driver ag-type field] + (merge + ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type field) + (when (#{:count :cum-count} ag-type) + {:base_type :type/Integer})))) diff --git a/resources/migrations/000_migrations.yaml b/resources/migrations/000_migrations.yaml index 5aa177bb228319e87d23fb1ccfcd86dcdcf4b99f..d07e5c3d8779fb2f08970ee0913f092a7d73bc7f 100644 --- a/resources/migrations/000_migrations.yaml +++ b/resources/migrations/000_migrations.yaml @@ -13,6 +13,14 @@ databaseChangeLog: name: quote_strategy value: LEGACY dbms: postgresql,h2 + - property: + name: timestamp_type + value: timestamp with time zone + dbms: postgresql,h2 + - property: + name: timestamp_type + value: timestamp(6) + dbms: mysql,mariadb - changeSet: id: '1' author: agilliland @@ -6224,20 +6232,47 @@ databaseChangeLog: remarks: 'Preferred ISO locale (language/country) code, e.g "en" or "en-US", for this User. Overrides site default.' type: varchar(5) +# Add Field `database_position` to keep the order in which fields are ordered in the DB, `custom_position` for custom +# position; and Table `field_order` setting. + + - changeSet: + id: 165 + author: sb + comment: 'Added field_order to Table and database_position to Field' + changes: + - addColumn: + tableName: metabase_field + columns: + - column: + name: database_position + type: int + defaultValueNumeric: 0 + constraints: + nullable: false + - column: + name: custom_position + type: int + defaultValueNumeric: 0 + constraints: + nullable: false + - addColumn: + tableName: metabase_table + columns: + - column: + name: field_order + type: varchar(254) + defaultValue: database + constraints: + nullable: false + - sql: + sql: update metabase_field set database_position = id + # Change field_values.updated_at and query_cache.updated_at from datetime to timestamp [with time zone] to get > # second resolution on MySQL. # # query_cache.updated_at was originally converted to a timestamp in 161, but we used `timestamp` instead of # `timestamp(6)`. It is converted correctly here. - - property: - name: timestamp_type - value: timestamp with time zone - dbms: postgresql,h2 - - property: - name: timestamp_type - value: timestamp(6) - dbms: mysql,mariadb - changeSet: id: 166 author: camsaul @@ -6327,3 +6362,16 @@ databaseChangeLog: tableName: native_query_snippet constraintName: idx_unique_name_database_id columnNames: name, database_id + +# Convert query execution from DATETIME to TIMESTAMP(6) so have normalize TZ +# offset and so MySQL/MariaDB has better than second precision + + - changeSet: + id: 168 + author: camsaul + comment: Added 0.36.0 + changes: + - modifyDataType: + tableName: query_execution + columnName: started_at + newDataType: ${timestamp_type} diff --git a/src/metabase/api/table.clj b/src/metabase/api/table.clj index 7979988699ba60ac5c9a81f34fae25d7ad42e4e1..3bb08c04cfe34cda3b647b472c066cb9c8964fbf 100644 --- a/src/metabase/api/table.clj +++ b/src/metabase/api/table.clj @@ -30,6 +30,10 @@ "Schema for a valid table visibility type." (apply s/enum (map name table/visibility-types))) +(def ^:private FieldOrder + "Schema for a valid table field ordering." + (apply s/enum (map name table/field-orderings))) + (api/defendpoint GET "/" "Get all `Tables`." [] @@ -50,36 +54,42 @@ ;; TODO: this should changed to `update-tables!` and update multiple tables in one db request (defn- update-table! [id {:keys [visibility_type] :as body}] - (api/write-check Table id) - (let [original-visibility-type (db/select-one-field :visibility_type Table :id id)] + (let [table (Table id)] + (api/write-check table) ;; always update visibility type; update display_name, show_in_getting_started, entity_type if non-nil; update ;; description and related fields if passed in (api/check-500 (db/update! Table id (assoc (u/select-keys-when body - :non-nil [:display_name :show_in_getting_started :entity_type] + :non-nil [:display_name :show_in_getting_started :entity_type :field_order] :present [:description :caveats :points_of_interest]) - :visibility_type visibility_type))) - (let [updated-table (Table id) - now-visible? (nil? (:visibility_type updated-table)) ; only Tables with `nil` visibility type are visible - was-visible? (nil? original-visibility-type) - became-visible? (and now-visible? (not was-visible?))] + :visibility_type visibility_type))) + (let [updated-table (Table id) + changed-field-order? (not= (:field_order updated-table) (:field_order table)) + now-visible? (nil? (:visibility_type updated-table)) ; only Tables with `nil` visibility type are visible + was-visible? (nil? (:visibility_type table)) + became-visible? (and now-visible? (not was-visible?))] (when became-visible? (log/info (u/format-color 'green (trs "Table ''{0}'' is now visible. Resyncing." (:name updated-table)))) (sync/sync-table! updated-table)) - updated-table))) + (if changed-field-order? + (do + (table/update-field-positions! updated-table) + (hydrate updated-table [:fields [:target :has_field_values] :dimensions :has_field_values])) + updated-table)))) (api/defendpoint PUT "/:id" "Update `Table` with ID." [id :as {{:keys [display_name entity_type visibility_type description caveats points_of_interest - show_in_getting_started], :as body} :body}] + show_in_getting_started field_order], :as body} :body}] {display_name (s/maybe su/NonBlankString) entity_type (s/maybe su/EntityTypeKeywordOrString) visibility_type (s/maybe TableVisibilityType) description (s/maybe su/NonBlankString) caveats (s/maybe su/NonBlankString) points_of_interest (s/maybe su/NonBlankString) - show_in_getting_started (s/maybe s/Bool)} + show_in_getting_started (s/maybe s/Bool) + field_order (s/maybe FieldOrder)} (update-table! id body)) (api/defendpoint PUT "/" @@ -370,4 +380,11 @@ [id] (-> id Table api/read-check related/related)) +(api/defendpoint PUT "/:id/fields/order" + "Reorder fields" + [id :as {field_order :body}] + {field_order [su/IntGreaterThanZero]} + (api/check-superuser) + (-> id Table api/check-404 (table/custom-order-fields! field_order))) + (api/define-routes) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index 4949c00c0c20c384e8b32827a14bba0c4b69dad9..e8dbd600bb6b44d88c152fdc52be08e36090c68f 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -330,7 +330,6 @@ Example impl: - (defmethod reducible-query :my-driver [_ query context respond] (with-open [results (run-query! query)] @@ -595,3 +594,11 @@ {:arglists '([driver inner-query])} dispatch-on-initialized-driver :hierarchy #'hierarchy) + +(defmulti default-field-order + "Return how fields should be sorted by default for this database." + {:added "0.36.0" :arglists '([driver])} + dispatch-on-initialized-driver + :hierarchy #'hierarchy) + +(defmethod default-field-order ::driver [_] :database) diff --git a/src/metabase/driver/sql_jdbc/sync.clj b/src/metabase/driver/sql_jdbc/sync.clj index 341d90fd5622c1f9bcd5c97b293e79793a6ce1a3..2e6b678ed2619613d237ff4574df22efe0f96925 100644 --- a/src/metabase/driver/sql_jdbc/sync.clj +++ b/src/metabase/driver/sql_jdbc/sync.clj @@ -5,6 +5,7 @@ [string :as str]] [clojure.java.jdbc :as jdbc] [clojure.tools.logging :as log] + [medley.core :as m] [metabase [driver :as driver] [util :as u]] @@ -94,7 +95,8 @@ ;; tablePattern "%" = match all tables (with-open [rs (.getTables metadata db-name-or-nil schema-or-nil "%" (into-array String ["TABLE" "VIEW" "FOREIGN TABLE" "MATERIALIZED VIEW"]))] - (vec (jdbc/metadata-result rs)))) + (mapv #(select-keys % [:table_name :remarks :table_schem]) + (jdbc/result-set-seq rs)))) (defn fast-active-tables "Default, fast implementation of `active-tables` best suited for DBs with lots of system tables (like Oracle). Fetch @@ -104,7 +106,7 @@ vs 60)." [driver ^DatabaseMetaData metadata & [db-name-or-nil]] (with-open [rs (.getSchemas metadata)] - (let [all-schemas (set (map :table_schem (jdbc/metadata-result rs))) + (let [all-schemas (set (map :table_schem (jdbc/result-set-seq rs))) schemas (set/difference all-schemas (excluded-schemas driver))] (set (for [schema schemas table (get-tables metadata schema db-name-or-nil)] @@ -153,13 +155,14 @@ [^DatabaseMetaData metadata, driver, {^String schema :schema, ^String table-name :name}, & [^String db-name-or-nil]] (with-open [rs (.getColumns metadata db-name-or-nil schema table-name nil)] (set - (for [{database-type :type_name - column-name :column_name - remarks :remarks} (jdbc/metadata-result rs)] + (for [[idx {database-type :type_name + column-name :column_name + remarks :remarks}] (m/indexed (jdbc/metadata-result rs))] (merge - {:name column-name - :database-type database-type - :base-type (database-type->base-type-or-warn driver database-type)} + {:name column-name + :database-type database-type + :base-type (database-type->base-type-or-warn driver database-type) + :database-position idx} (when (not (str/blank? remarks)) {:field-comment remarks}) (when-let [special-type (calculated-special-type driver column-name database-type)] diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj index fdc5e5d64d388060eb9dc2a10d54dc6ecad5b2d9..1d1b3a404506aa45b85d0ab81798e35d00421fc0 100644 --- a/src/metabase/models/database.clj +++ b/src/metabase/models/database.clj @@ -137,14 +137,14 @@ {:modifiers [:DISTINCT]})))) (defn pk-fields - "Return all the primary key `Fields` associated with this DATABASE." + "Return all the primary key `Fields` associated with this `database`." [{:keys [id]}] (let [table-ids (db/select-ids 'Table, :db_id id, :active true)] (when (seq table-ids) (db/select 'Field, :table_id [:in table-ids], :special_type (mdb/isa :type/PK))))) (defn schema-exists? - "Does DATABASE have any tables with SCHEMA?" + "Does `database` have any tables with `schema`?" ^Boolean [{:keys [id]}, schema] (db/exists? 'Table :db_id id, :schema (some-> schema name))) diff --git a/src/metabase/models/permissions.clj b/src/metabase/models/permissions.clj index 06d2272496f035e10a65b73096da66615c32c893..75b0b98c74498430129aec681f339650db4bfd32 100644 --- a/src/metabase/models/permissions.clj +++ b/src/metabase/models/permissions.clj @@ -209,23 +209,18 @@ (valid-object-path? path))) permissions-set))) - (defn set-has-full-permissions? "Does `permissions-set` grant *full* access to object with `path`?" - {:style/indent 1} ^Boolean [permissions-set path] (boolean (some #(is-permissions-for-object? % path) permissions-set))) (defn set-has-partial-permissions? "Does `permissions-set` grant access full access to object with `path` *or* to a descendant of it?" - {:style/indent 1} ^Boolean [permissions-set path] (boolean (some #(is-partial-permissions-for-object? % path) permissions-set))) - (s/defn set-has-full-permissions-for-set? :- s/Bool "Do the permissions paths in `permissions-set` grant *full* access to all the object paths in `object-paths-set`?" - {:style/indent 1} [permissions-set :- #{UserPath}, object-paths-set :- #{ObjectPath}] (every? (partial set-has-full-permissions? permissions-set) object-paths-set)) @@ -233,7 +228,6 @@ (s/defn set-has-partial-permissions-for-set? :- s/Bool "Do the permissions paths in `permissions-set` grant *partial* access to all the object paths in `object-paths-set`? (`permissions-set` must grant partial access to *every* object in `object-paths-set` set)." - {:style/indent 1} [permissions-set :- #{UserPath}, object-paths-set :- #{ObjectPath}] (every? (partial set-has-partial-permissions? permissions-set) object-paths-set)) diff --git a/src/metabase/models/table.clj b/src/metabase/models/table.clj index 835cd583737ebd555e691176ee4d2da44b9b99e0..3d99c17753248235985a191fe24bce04f984233e 100644 --- a/src/metabase/models/table.clj +++ b/src/metabase/models/table.clj @@ -1,6 +1,8 @@ (ns metabase.models.table - (:require [metabase + (:require [honeysql.core :as hsql] + [metabase [db :as mdb] + [driver :as driver] [util :as u]] [metabase.models [database :refer [Database]] @@ -22,6 +24,14 @@ (Basically any non-nil value is a reason for hiding the table.)" #{:hidden :technical :cruft}) +(def ^:const field-orderings + "Valid values for `Table.field_order`. + `:database` - use the same order as in the table definition in the DB; + `:alphabetical` - order alphabetically by name; + `:custom` - the user manually set the order in the data model + `:smart` - Try to be smart and order like you'd usually want it: first PK, followed by `:type/Name`s, then `:type/Temporal`s, and from there on in alphabetical order." + #{:database :alphabetical :custom :smart}) + (models/defmodel Table :metabase_table) @@ -29,7 +39,8 @@ ;;; --------------------------------------------------- Lifecycle ---------------------------------------------------- (defn- pre-insert [table] - (let [defaults {:display_name (humanization/name->human-readable-name (:name table))}] + (let [defaults {:display_name (humanization/name->human-readable-name (:name table)) + :field_order (driver/default-field-order (-> table :db_id Database :engine))}] (merge defaults table))) (defn- pre-delete [{:keys [db_id schema id]}] @@ -51,7 +62,8 @@ (merge models/IModelDefaults {:hydration-keys (constantly [:table]) :types (constantly {:entity_type :keyword - :visibility_type :keyword}) + :visibility_type :keyword + :field_order :keyword}) :properties (constantly {:timestamped? true}) :pre-insert pre-insert :pre-delete pre-delete}) @@ -62,16 +74,61 @@ :perms-objects-set perms-objects-set})) +;;; ------------------------------------------------ Field ordering ------------------------------------------------- + +(defn field-order-rule + "How should we order fields." + [_] + [[:position :asc] [:%lower.name :asc]]) + +(defn update-field-positions! + "Update `:position` of field belonging to table `table` accordingly to `:field_order`" + [table] + (doall + (map-indexed (fn [new-position field] + (db/update! Field (u/get-id field) :position new-position)) + ;; Can't use `select-field` as that returns a set while we need an ordered list + (db/select [Field :id] + :table_id (u/get-id table) + {:order-by (case (:field_order table) + :custom [[:custom_position :asc]] + :smart [[(hsql/call :case + (mdb/isa :special_type :type/PK) 0 + (mdb/isa :special_type :type/Name) 1 + (mdb/isa :special_type :type/Temporal) 2 + :else 3) + :asc] + [:%lower.name :asc]] + :database [[:database_position :asc]] + :alphabetical [[:%lower.name :asc]])})))) + +(defn- valid-field-order? + "Field ordering is valid if all the fields from a given table are present and only from that table." + [table field-ordering] + (= (db/select-ids Field :table_id (u/get-id table)) (set field-ordering))) + +(defn custom-order-fields! + "Set field order to `field-order`." + [table field-order] + {:pre [(valid-field-order? table field-order)]} + (db/update! Table (u/get-id table) :field_order :custom) + (doall + (map-indexed (fn [position field-id] + (db/update! Field field-id {:position position + :custom_position position})) + field-order))) + + ;;; --------------------------------------------------- Hydration ---------------------------------------------------- -(defn fields +(defn ^:hydrate fields "Return the Fields belonging to a single `table`." - [{:keys [id]}] + [{:keys [id] :as table}] (db/select Field :table_id id :active true :visibility_type [:not= "retired"] - {:order-by [[:position :asc] [:name :asc]]})) + {:order-by (field-order-rule table)})) (defn metrics "Retrieve the Metrics for a single `table`." @@ -86,11 +143,11 @@ (defn field-values "Return the FieldValues for all Fields belonging to a single `table`." {:hydrate :field_values, :arglists '([table])} - [{:keys [id]}] + [{:keys [id] :as table}] (let [field-ids (db/select-ids Field :table_id id :visibility_type "normal" - {:order-by [[:position :asc] [:name :asc]]})] + {:order-by (field-order-rule table)})] (when (seq field-ids) (db/select-field->field :field_id :values FieldValues, :field_id [:in field-ids])))) @@ -131,7 +188,6 @@ (defn with-fields "Efficiently hydrate the Fields for a collection of `tables`." - {:batched-hydrate :fields} [tables] (with-objects :fields (fn [table-ids] @@ -139,7 +195,7 @@ :active true :table_id [:in table-ids] :visibility_type [:not= "retired"] - {:order-by [[:position :asc] [:name :asc]]})) + {:order-by (field-order-rule tables)})) tables)) diff --git a/src/metabase/query_processor/middleware/add_implicit_clauses.clj b/src/metabase/query_processor/middleware/add_implicit_clauses.clj index 61924182a71ffb2ebe31504ef753f16813b535b6..ee10ec8e6e07b7f15a3d12c260bcef2ed5b8eb38 100644 --- a/src/metabase/query_processor/middleware/add_implicit_clauses.clj +++ b/src/metabase/query_processor/middleware/add_implicit_clauses.clj @@ -1,15 +1,15 @@ (ns metabase.query-processor.middleware.add-implicit-clauses "Middlware for adding an implicit `:fields` and `:order-by` clauses to certain queries." (:require [clojure.tools.logging :as log] - [honeysql.core :as hsql] [metabase - [db :as mdb] [types :as types] [util :as u]] [metabase.mbql [schema :as mbql.s] [util :as mbql.u]] - [metabase.models.field :refer [Field]] + [metabase.models + [field :refer [Field]] + [table :as table :refer [Table]]] [metabase.query-processor [error-type :as error-type] [interface :as qp.i] @@ -24,38 +24,32 @@ ;;; | Add Implicit Fields | ;;; +----------------------------------------------------------------------------------------------------------------+ -;; this is a fn because we don't want to call mdb/isa before type hierarchy is loaded! -(defn- default-sort-rules [] - [ ;; sort first by position, - [:position :asc] - ;; or if that's the same, sort PKs first, followed by names, followed by everything else - [(hsql/call :case - (mdb/isa :special_type :type/PK) 0 - (mdb/isa :special_type :type/Name) 1 - :else 2) - :asc] - ;; finally, sort by name (case-insensitive) - [:%lower.name :asc]]) - -(defn- table->sorted-fields [table-or-id] +(defn- table->sorted-fields + [table-id] (db/select [Field :id :base_type :special_type] - :table_id (u/get-id table-or-id) + :table_id table-id :active true :visibility_type [:not-in ["sensitive" "retired"]] :parent_id nil - ;; I suppose if we wanted to we could make the `order-by` rules swappable with something other set of rules - {:order-by (default-sort-rules)})) + {:order-by (table/field-order-rule (Table table-id))})) (s/defn sorted-implicit-fields-for-table :- mbql.s/Fields "For use when adding implicit Field IDs to a query. Return a sequence of field clauses, sorted by the rules listed in `metabase.query-processor.sort`, for all the Fields in a given Table." [table-id :- su/IntGreaterThanZero] - (for [field (table->sorted-fields table-id)] - (if (types/temporal-field? field) - ;; implicit datetime Fields get bucketing of `:default`. This is so other middleware doesn't try to give it - ;; default bucketing of `:day` - [:datetime-field [:field-id (u/get-id field)] :default] - [:field-id (u/get-id field)]))) + (let [fields (table->sorted-fields table-id)] + (when (empty? fields) + (throw (ex-info (tru "No fields found for table {0}." (pr-str (:name (qp.store/table table-id)))) + {:table-id table-id + :type error-type/invalid-query}))) + (mapv + (fn [field] + (if (types/temporal-field? field) + ;; implicit datetime Fields get bucketing of `:default`. This is so other middleware doesn't try to give it + ;; default bucketing of `:day` + [:datetime-field [:field-id (u/get-id field)] :default] + [:field-id (u/get-id field)])) + fields))) (s/defn ^:private source-metadata->fields :- mbql.s/Fields "Get implicit Fields for a query with a `:source-query` that has `source-metadata`." diff --git a/src/metabase/sync/interface.clj b/src/metabase/sync/interface.clj index 08071c80bde84446bde9435e2aa51d5173ec8e76..4b0bef63ed735a2a47d3065ef55d999fcbd2f727 100644 --- a/src/metabase/sync/interface.clj +++ b/src/metabase/sync/interface.clj @@ -13,6 +13,7 @@ "Schema for the expected output of `describe-database` for a Table." {:name su/NonBlankString :schema (s/maybe su/NonBlankString) + ;; `:description` in this case should be a column/remark on the Table, if there is one. (s/optional-key :description) (s/maybe su/NonBlankString)}) (def DatabaseMetadata @@ -25,6 +26,7 @@ {:name su/NonBlankString :database-type (s/maybe su/NonBlankString) ; blank if the Field is all NULL & untyped, i.e. in Mongo :base-type su/FieldType + :database-position su/IntGreaterThanOrEqualToZero (s/optional-key :special-type) (s/maybe su/FieldType) (s/optional-key :field-comment) (s/maybe su/NonBlankString) (s/optional-key :pk?) s/Bool diff --git a/src/metabase/sync/sync_metadata/fields.clj b/src/metabase/sync/sync_metadata/fields.clj index b80c3a3bdea230e67fa09775d3d02a4468e8ddc8..972d83002c105caa6d9778814243c23d7c7a516b 100644 --- a/src/metabase/sync/sync_metadata/fields.clj +++ b/src/metabase/sync/sync_metadata/fields.clj @@ -64,7 +64,7 @@ metadata coming back from the DB/drivers is the same as last timw." [db-metadata :- #{i/TableMetadataField}] (->> db-metadata - (map (juxt :name :database-type :base-type :special-type :pk? :nested-fields :custom :field-comment)) + (map (juxt :name :database-type :base-type :special-type :pk? :nested-fields :custom :field-comment :database-position)) ;; We need a predictable sort order as the hash will be different if the order is different (sort-by first) sync-util/calculate-hash)) diff --git a/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj b/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj index bf8694108e3dd1853ec0202a2ec1fb02ffbe14b7..622eb2af33b778d4de895a43c4a35fe5aa25402d 100644 --- a/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj +++ b/src/metabase/sync/sync_metadata/fields/fetch_metadata.clj @@ -4,7 +4,9 @@ `metabase.sync.sync-metadata.fields.*` namespaces to determine what sync operations need to be performed by comparing the differences in the two sets of Metadata." (:require [medley.core :as m] - [metabase.models.field :as field :refer [Field]] + [metabase.models + [field :as field :refer [Field]] + [table :as table]] [metabase.sync [fetch-metadata :as fetch-metadata] [interface :as i]] @@ -20,14 +22,15 @@ (s/defn ^:private fields->parent-id->fields :- {common/ParentID #{common/TableMetadataFieldWithID}} [fields :- (s/maybe [i/FieldInstance])] (->> (for [field fields] - {:parent-id (:parent_id field) - :id (:id field) - :name (:name field) - :database-type (:database_type field) - :base-type (:base_type field) - :special-type (:special_type field) - :pk? (isa? (:special_type field) :type/PK) - :field-comment (:description field)}) + {:parent-id (:parent_id field) + :id (:id field) + :name (:name field) + :database-type (:database_type field) + :base-type (:base_type field) + :special-type (:special_type field) + :pk? (isa? (:special_type field) :type/PK) + :field-comment (:description field) + :database-position (:database_position field)}) ;; make a map of parent-id -> set of child Fields (group-by :parent-id) ;; remove the parent ID because the Metadata from `describe-table` won't have it. Save the results as a set @@ -60,9 +63,10 @@ (s/defn ^:private table->fields :- [i/FieldInstance] "Fetch active Fields from the Metabase application database for a given `table`." [table :- i/TableInstance] - (db/select [Field :name :database_type :base_type :special_type :parent_id :id :description] - :table_id (u/get-id table) - :active true)) + (db/select [Field :name :database_type :base_type :special_type :parent_id :id :description :database_position] + :table_id (u/get-id table) + :active true + {:order-by (table/field-order-rule table)})) (s/defn our-metadata :- #{common/TableMetadataFieldWithID} "Return information we have about Fields for a `table` in the application database in (almost) exactly the same diff --git a/src/metabase/sync/sync_metadata/fields/sync_instances.clj b/src/metabase/sync/sync_metadata/fields/sync_instances.clj index 3b67b74e7b051525b5a0e7695f0e190f8675157f..b5fd797853bf0eddcb492f9875dda04982e0676e 100644 --- a/src/metabase/sync/sync_metadata/fields/sync_instances.clj +++ b/src/metabase/sync/sync_metadata/fields/sync_instances.clj @@ -43,15 +43,17 @@ [table :- i/TableInstance, new-field-metadatas :- [i/TableMetadataField], parent-id :- common/ParentID] (when (seq new-field-metadatas) (db/insert-many! Field - (for [{:keys [database-type base-type field-comment], field-name :name :as field} new-field-metadatas] - {:table_id (u/get-id table) - :name field-name - :display_name (humanization/name->human-readable-name field-name) - :database_type (or database-type "NULL") ; placeholder for Fields w/ no type info (e.g. Mongo) & all NULL - :base_type base-type - :special_type (common/special-type field) - :parent_id parent-id - :description field-comment})))) + (for [{:keys [database-type base-type field-comment database-position], field-name :name :as field} new-field-metadatas] + {:table_id (u/get-id table) + :name field-name + :display_name (humanization/name->human-readable-name field-name) + :database_type (or database-type "NULL") ; placeholder for Fields w/ no type info (e.g. Mongo) & all NULL + :base_type base-type + :special_type (common/special-type field) + :parent_id parent-id + :description field-comment + :position database-position + :database_position database-position})))) (s/defn ^:private create-or-reactivate-fields! :- (s/maybe [i/FieldInstance]) "Create (or reactivate) Metabase Field object(s) for any Fields in `new-field-metadatas`. Does *NOT* recursively diff --git a/src/metabase/sync/sync_metadata/fields/sync_metadata.clj b/src/metabase/sync/sync_metadata/fields/sync_metadata.clj index 36fd24227dc4f4e7637b8f2928c04d18f7e308a6..242ecb15e561172d8a00dd6f4a0cbd1008286346 100644 --- a/src/metabase/sync/sync_metadata/fields/sync_metadata.clj +++ b/src/metabase/sync/sync_metadata/fields/sync_metadata.clj @@ -20,15 +20,17 @@ "Update the metadata for a Metabase Field as needed if any of the info coming back from the DB has changed. Syncs base type, database type, special type, and comments/remarks; returns `1` if the Field was updated; `0` otherwise." [table :- i/TableInstance, field-metadata :- i/TableMetadataField, metabase-field :- common/TableMetadataFieldWithID] - (let [{old-database-type :database-type - old-base-type :base-type - old-field-comment :field-comment - old-special-type :special-type} metabase-field - {new-database-type :database-type - new-base-type :base-type - new-field-comment :field-comment} field-metadata - new-database-type (or new-database-type "NULL") - new-special-type (common/special-type field-metadata) + (let [{old-database-type :database-type + old-base-type :base-type + old-field-comment :field-comment + old-special-type :special-type + old-database-position :database-position} metabase-field + {new-database-type :database-type + new-base-type :base-type + new-field-comment :field-comment + new-database-position :database-position} field-metadata + new-database-type (or new-database-type "NULL") + new-special-type (common/special-type field-metadata) new-db-type? (not= old-database-type new-database-type) @@ -44,6 +46,10 @@ (and (str/blank? old-field-comment) (not (str/blank? new-field-comment))) + + new-database-position? + (not= old-database-position new-database-position) + ;; calculate combined updates updates (merge @@ -68,7 +74,13 @@ (when new-comment? (log/info (trs "Comment has been added for {0}." (common/field-metadata-name-for-logging table metabase-field))) - {:description new-field-comment}))] + {:description new-field-comment}) + (when new-database-position? + (log/info (trs "Database position of {0} has changed from ''{1}'' to ''{2}''." + (common/field-metadata-name-for-logging table metabase-field) + old-database-position + new-database-position)) + {:database_position new-database-position}))] ;; if any updates need to be done, do them and return 1 (because 1 Field was updated), otherwise return 0 (if (and (seq updates) (db/update! Field (u/get-id metabase-field) updates)) diff --git a/test/expectations.clj b/test/expectations.clj index af1d26f622c56d8830067ab3369efe1d9a3ba22d..84d31218b628b915281c4f5e6c5d147310062ee7 100644 --- a/test/expectations.clj +++ b/test/expectations.clj @@ -137,8 +137,8 @@ ;; expecting it. (when-not (env/env :drivers) (t/testing "Don't write any new tests using expect!" - (t/is (<= total-expect-forms 1889)) - (t/is (<= total-namespaces-using-expect 124)))))) + (t/is (<= total-expect-forms 1778)) + (t/is (<= total-namespaces-using-expect 114)))))) (defmacro ^:deprecated expect "Simple macro that simulates converts an Expectations-style `expect` form into a `clojure.test` `deftest` form." diff --git a/test/metabase/api/automagic_dashboards_test.clj b/test/metabase/api/automagic_dashboards_test.clj index c01d74c0e9c0354d0e929145287854e482477741..dd13b410a75276a5728435bc28a26477479da3be 100644 --- a/test/metabase/api/automagic_dashboards_test.clj +++ b/test/metabase/api/automagic_dashboards_test.clj @@ -162,9 +162,9 @@ (de.test/with-test-domain-entity-specs (mt/with-model-cleanup ['Card 'Collection] (transforms/apply-transform! (mt/id) "PUBLIC" (first @transforms.specs/transform-specs)) - (is (= [[4 1 10.0646 -165.374 "Red Medicine" 3 1.5 4 3 2 1] - [11 2 34.0996 -118.329 "Stout Burgers & Beers" 2 2.0 11 2 1 1] - [11 3 34.0406 -118.428 "The Apple Pan" 2 2.0 11 2 1 1]] + (is (= [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5 4 3 2 1] + [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0 11 2 1 1] + [3 "The Apple Pan" 11 34.0406 -118.428 2 2.0 11 2 1 1]] (api-call "transform/%s" ["Test transform"] #(revoke-collection-permissions! (transforms.materialize/get-collection "Test transform")) diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index 8102890af069aa049bfe2852d1adc37f55979468..f3f19280bc58aa843c7a96e8f3bd145d3de3ba9b 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -28,13 +28,13 @@ [db :as db] [hydrate :as hydrate]])) -(use-fixtures :once (fixtures/initialize :plugins)) +(use-fixtures :once (fixtures/initialize :db :plugins)) ;; HELPER FNS (driver/register! ::test-driver - :parent :sql-jdbc - :abstract? true) + :parent :sql-jdbc + :abstract? true) (defmethod driver/connection-properties ::test-driver [_] @@ -44,21 +44,6 @@ [_ _] true) -(def ^:private default-db-details - {:engine "h2" - :name "test-data" - :is_sample false - :is_full_sync true - :is_on_demand false - :description nil - :caveats nil - :points_of_interest nil - :cache_field_values_schedule "0 50 0 * * ? *" - :metadata_sync_schedule "0 50 * * * ? *" - :options nil - :timezone nil - :auto_run_queries true}) - (defn- db-details "Return default column values for a database (either the test database, via `(mt/db)`, or optionally passed in)." ([] @@ -66,29 +51,16 @@ ([{driver :engine, :as db}] (merge - default-db-details + (mt/object-defaults Database) (select-keys db [:created_at :id :details :updated_at :timezone :name]) {:engine (u/qualified-name (:engine db)) :features (map u/qualified-name (driver.u/features driver))}))) -(def ^:private default-table-details - {:description nil - :entity_name nil - :entity_type "entity/GenericTable" - :caveats nil - :points_of_interest nil - :visibility_type nil - :active true - :show_in_getting_started false}) - (defn- table-details [table] - (-> default-table-details - (merge - (select-keys table [:active :created_at :db_id :description :display_name :entity_name :entity_type :fields_hash - :id :name :rows :schema :updated_at :visibility_type])) - (update :entity_type (fn [entity-type] - (when entity-type - (str "entity/" (name entity-type))))) + (-> (merge (mt/obj->json->obj (mt/object-defaults Table)) + (select-keys table [:active :created_at :db_id :description :display_name :entity_name :entity_type + :fields_hash :id :name :rows :schema :updated_at :visibility_type])) + (update :entity_type #(when % (str "entity/" (name %)))) (update :visibility_type #(when % (name %))))) (defn- expected-tables [db-or-id] @@ -96,23 +68,14 @@ :db_id (u/get-id db-or-id), :active true {:order-by [[:%lower.schema :asc] [:%lower.display_name :asc]]}))) -(def ^:private default-field-details - {:description nil - :caveats nil - :points_of_interest nil - :active true - :position 0 - :target nil - :preview_display true - :parent_id nil - :settings nil}) - (defn- field-details [field] - (merge - default-field-details - (select-keys - field - [:updated_at :id :created_at :last_analyzed :fingerprint :fingerprint_version :fk_target_field_id :position]))) + (mt/derecordize + (merge + (mt/object-defaults Field) + {:target nil} + (select-keys + field + [:updated_at :id :created_at :last_analyzed :fingerprint :fingerprint_version :fk_target_field_id :position])))) (defn- add-schedules [db] (assoc db :schedules {:cache_field_values {:schedule_day nil @@ -186,7 +149,7 @@ (testing "POST /api/database" (testing "Check that we can create a Database" (is (schema= (merge - (m/map-vals s/eq default-db-details) + (m/map-vals s/eq (mt/object-defaults Database)) {:created_at java.time.temporal.Temporal :engine (s/eq ::test-driver) :id su/IntGreaterThanZero @@ -245,43 +208,46 @@ (deftest fetch-database-metadata-test (testing "GET /api/database/:id/metadata" - (is (= (merge default-db-details + (is (= (merge (dissoc (mt/object-defaults Database) :details) (select-keys (mt/db) [:created_at :id :updated_at :timezone]) {:engine "h2" :name "test-data" :features (map u/qualified-name (driver.u/features :h2)) :tables [(merge - default-table-details + (mt/obj->json->obj (mt/object-defaults Table)) (db/select-one [Table :created_at :updated_at :fields_hash] :id (mt/id :categories)) {:schema "PUBLIC" :name "CATEGORIES" :display_name "Categories" + :entity_type "entity/GenericTable" :fields [(merge (field-details (Field (mt/id :categories :id))) - {:table_id (mt/id :categories) - :special_type "type/PK" - :name "ID" - :display_name "ID" - :database_type "BIGINT" - :base_type "type/BigInteger" - :visibility_type "normal" - :has_field_values "none"}) + {:table_id (mt/id :categories) + :special_type "type/PK" + :name "ID" + :display_name "ID" + :database_type "BIGINT" + :base_type "type/BigInteger" + :visibility_type "normal" + :has_field_values "none" + :database_position 0}) (merge (field-details (Field (mt/id :categories :name))) - {:table_id (mt/id :categories) - :special_type "type/Name" - :name "NAME" - :display_name "Name" - :database_type "VARCHAR" - :base_type "type/Text" - :visibility_type "normal" - :has_field_values "list"})] + {:table_id (mt/id :categories) + :special_type "type/Name" + :name "NAME" + :display_name "Name" + :database_type "VARCHAR" + :base_type "type/Text" + :visibility_type "normal" + :has_field_values "list" + :database_position 1})] :segments [] :metrics [] :rows nil :id (mt/id :categories) :db_id (mt/id)})]}) - (let [resp ((mt/user->client :rasta) :get 200 (format "database/%d/metadata" (mt/id)))] + (let [resp (mt/derecordize ((mt/user->client :rasta) :get 200 (format "database/%d/metadata" (mt/id))))] (assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp)))))))) (deftest fetch-database-metadata-include-hidden-test diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj index f80ff2b9e40ef9b726bc68065d3a20685940a020..2d902159a023065c66a56667b58d5ade545b8703 100644 --- a/test/metabase/api/field_test.clj +++ b/test/metabase/api/field_test.clj @@ -1,27 +1,19 @@ (ns metabase.api.field-test "Tests for `/api/field` endpoints." (:require [clojure.test :refer :all] - [expectations :refer [expect]] + [medley.core :as m] [metabase + [models :refer [Database Field FieldValues Table]] [test :as mt] [util :as u]] [metabase.api.field :as field-api] [metabase.driver.util :as driver.u] - [metabase.models - [field :refer [Field]] - [field-values :refer [FieldValues]] - [table :refer [Table]]] - [metabase.test - [fixtures :as fixtures] - [util :as tu]] - [metabase.test.data.users :as test-users] - [metabase.test.util.log :as tu.log] + [metabase.test.fixtures :as fixtures] [metabase.timeseries-query-processor-test.util :as tqp.test] [ring.util.codec :as codec] [toucan [db :as db] - [hydrate :refer [hydrate]]] - [toucan.util.test :as tt])) + [hydrate :refer [hydrate]]])) (use-fixtures :once (fixtures/initialize :plugins)) @@ -29,136 +21,125 @@ (defn- db-details [] (merge - (select-keys (mt/db) [:id :created_at :updated_at :timezone]) - {:engine "h2" - :caveats nil - :points_of_interest nil - :name "test-data" - :is_sample false - :is_full_sync true - :is_on_demand false - :description nil - :features (mapv u/qualified-name (driver.u/features :h2)) - :cache_field_values_schedule "0 50 0 * * ? *" - :metadata_sync_schedule "0 50 * * * ? *" - :options nil - :auto_run_queries true})) - -;; ## GET /api/field/:id -(expect - (merge - (db/select-one [Field :created_at :updated_at :last_analyzed :fingerprint :fingerprint_version] - :id (mt/id :users :name)) - {:description nil - :table_id (mt/id :users) - :table (merge - (db/select-one [Table :created_at :updated_at :fields_hash] :id (mt/id :users)) - {:description nil - :entity_type "entity/UserTable" - :visibility_type nil - :db (db-details) - :schema "PUBLIC" - :name "USERS" - :display_name "Users" - :rows nil - :entity_name nil - :active true - :id (mt/id :users) - :db_id (mt/id) - :caveats nil - :points_of_interest nil - :show_in_getting_started false}) - :special_type "type/Name" - :name "NAME" - :display_name "Name" - :caveats nil - :points_of_interest nil - :active true - :id (mt/id :users :name) - :visibility_type "normal" - :position 0 - :preview_display true - :database_type "VARCHAR" - :base_type "type/Text" - :has_field_values "list" - :fk_target_field_id nil - :parent_id nil - :dimensions [] - :name_field nil - :settings nil}) - ((test-users/user->client :rasta) :get 200 (format "field/%d" (mt/id :users :name)))) - - - -;;; ------------------------------------------- GET /api/field/:id/summary ------------------------------------------- - -(expect - [["count" 75] ; why doesn't this come back as a dictionary ? - ["distincts" 75]] - ((test-users/user->client :rasta) :get 200 (format "field/%d/summary" (mt/id :categories :name)))) - - -;;; ----------------------------------------------- PUT /api/field/:id ----------------------------------------------- + (select-keys (mt/db) [:id :timezone]) + (dissoc (mt/object-defaults Database) :details) + {:engine "h2" + :name "test-data" + :features (mapv u/qualified-name (driver.u/features :h2)) + :timezone "UTC"})) + +(deftest get-field-test + (testing "GET /api/field/:id" + (is (= (-> (merge + (mt/object-defaults Field) + (db/select-one [Field :created_at :updated_at :last_analyzed :fingerprint :fingerprint_version :database_position] + :id (mt/id :users :name)) + {:table_id (mt/id :users) + :table (merge + (mt/obj->json->obj (mt/object-defaults Table)) + (db/select-one [Table :created_at :updated_at :fields_hash] :id (mt/id :users)) + {:description nil + :entity_type "entity/UserTable" + :visibility_type nil + :db (db-details) + :schema "PUBLIC" + :name "USERS" + :display_name "Users" + :rows nil + :entity_name nil + :active true + :id (mt/id :users) + :db_id (mt/id) + :caveats nil + :points_of_interest nil + :show_in_getting_started false}) + :special_type "type/Name" + :name "NAME" + :display_name "Name" + :position 1 + :id (mt/id :users :name) + :visibility_type "normal" + :database_type "VARCHAR" + :base_type "type/Text" + :has_field_values "list" + :dimensions [] + :name_field nil}) + (m/dissoc-in [:table :db :updated_at] [:table :db :created_at])) + (-> ((mt/user->client :rasta) :get 200 (format "field/%d" (mt/id :users :name))) + (m/dissoc-in [:table :db :updated_at] [:table :db :created_at])))))) + +(deftest get-field-summary-test + (testing "GET /api/field/:id/summary" + ;; TODO -- why doesn't this come back as a dictionary ? + (is (= [["count" 75] + ["distincts" 75]] + ((mt/user->client :rasta) :get 200 (format "field/%d/summary" (mt/id :categories :name))))))) (defn simple-field-details [field] (select-keys field [:name :display_name :description :visibility_type :special_type :fk_target_field_id])) -;; test that we can do basic field update work, including unsetting some fields such as special-type -(expect - [{:name "Field Test" - :display_name "Field Test" - :description nil - :special_type nil - :visibility_type :normal - :fk_target_field_id nil} - {:name "Field Test" - :display_name "yay" - :description "foobar" - :special_type :type/Name - :visibility_type :sensitive - :fk_target_field_id nil} - {:name "Field Test" - :display_name "yay" - :description nil - :special_type nil - :visibility_type :sensitive - :fk_target_field_id nil}] - (tt/with-temp* [Field [{field-id :id} {:name "Field Test"}]] - (let [original-val (simple-field-details (Field field-id))] - ;; set it - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id) {:name "something else" - :display_name "yay" - :description "foobar" - :special_type :type/Name - :visibility_type :sensitive}) - (let [updated-val (simple-field-details (Field field-id))] - ;; unset it - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id) {:description nil - :special_type nil}) - [original-val - updated-val - (simple-field-details (Field field-id))])))) - -;; when we set the special-type from :type/FK to something else, make sure fk_target_field_id is set to nil -(expect - {1 true - 2 nil} - (tt/with-temp* [Field [{fk-field-id :id}] - Field [{field-id :id} {:special_type :type/FK, :fk_target_field_id fk-field-id}]] - (let [original-val (boolean (db/select-one-field :fk_target_field_id Field, :id field-id))] - ;; unset the :type/FK special-type - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type :type/Name}) - (array-map - 1 original-val - 2 (db/select-one-field :fk_target_field_id Field, :id field-id))))) - - -;; check that you *can* set it if it *is* the proper base type -(expect - :type/UNIXTimestampSeconds - (tt/with-temp* [Field [{field-id :id} {:base_type :type/Integer}]] - ((test-users/user->client :crowberto) :put 200 (str "field/" field-id) {:special_type :type/UNIXTimestampSeconds}) - (db/select-one-field :special_type Field, :id field-id))) +(deftest update-field-test + (testing "PUT /api/field/:id" + (testing "test that we can do basic field update work, including unsetting some fields such as special-type" + (mt/with-temp Field [{field-id :id} {:name "Field Test"}] + (let [original-val (simple-field-details (Field field-id))] + (testing "orignal value" + (is (= {:name "Field Test" + :display_name "Field Test" + :description nil + :special_type nil + :visibility_type :normal + :fk_target_field_id nil} + original-val))) + ;; set it + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id) {:name "something else" + :display_name "yay" + :description "foobar" + :special_type :type/Name + :visibility_type :sensitive}) + (let [updated-val (simple-field-details (Field field-id))] + (testing "updated value" + (is (= {:name "Field Test" + :display_name "yay" + :description "foobar" + :special_type :type/Name + :visibility_type :sensitive + :fk_target_field_id nil} + updated-val))) + ;; unset it + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id) {:description nil + :special_type nil}) + (testing "response" + (is (= {:name "Field Test" + :display_name "yay" + :description nil + :special_type nil + :visibility_type :sensitive + :fk_target_field_id nil} + (simple-field-details (Field field-id))))))))))) + +(deftest remove-fk-special-type-test + (testing "PUT /api/field/:id" + (testing "when we set the special-type from `:type/FK` to something else, make sure `:fk_target_field_id` is set to nil" + (mt/with-temp* [Field [{fk-field-id :id}] + Field [{field-id :id} {:special_type :type/FK, :fk_target_field_id fk-field-id}]] + (let [original-val (boolean (db/select-one-field :fk_target_field_id Field, :id field-id))] + (testing "before API call" + (is (= true + original-val))) + ;; unset the :type/FK special-type + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type :type/Name}) + (testing "after API call" + (is (= nil + (db/select-one-field :fk_target_field_id Field, :id field-id))))))))) + +(deftest update-fk-target-field-id-test + (testing "PUT /api/field/:id" + (testing "check that you *can* set `:fk_target_field_id` if it *is* the proper base type" + (mt/with-temp Field [{field-id :id} {:base_type :type/Integer}] + ((mt/user->client :crowberto) :put 200 (str "field/" field-id) {:special_type :type/UNIXTimestampSeconds}) + (is (= :type/UNIXTimestampSeconds + (db/select-one-field :special_type Field, :id field-id))))))) (defn- field->field-values "Fetch the `FieldValues` object that corresponds to a given `Field`." @@ -168,124 +149,120 @@ (defn- field-values-id [table-key field-key] (:id (field->field-values table-key field-key))) -;; ## GET /api/field/:id/values -;; Should return something useful for a field whose `has_field_values` is `list` -(expect - {:values [[1] [2] [3] [4]], :field_id (mt/id :venues :price)} - (do - ;; clear out existing human_readable_values in case they're set - (db/update! FieldValues (field-values-id :venues :price) - :human_readable_values nil) - ;; now update the values via the API - ((test-users/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :venues :price))))) - -;; Should return nothing for a field whose `has_field_values` is not `list` -(expect - {:values [], :field_id (mt/id :venues :id)} - ((test-users/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :venues :id)))) - -;; Sensisitive fields do not have field values and should return empty -(expect - {:values [], :field_id (mt/id :users :password)} - ((test-users/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :users :password)))) - - -;;; ------------------------------------------- POST /api/field/:id/values ------------------------------------------- +(deftest field-values-test + (testing "GET /api/field/:id/values" + (testing "Should return something useful for a field whose `has_field_values` is `list`" + (mt/with-temp-copy-of-db + ;; clear out existing human_readable_values in case they're set + (when-let [id (field-values-id :venues :price)] + (db/update! FieldValues id :human_readable_values nil)) + ;; now update the values via the API + (is (= {:values [[1] [2] [3] [4]], :field_id (mt/id :venues :price)} + ((mt/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :venues :price))))))) + + (testing "Should return nothing for a field whose `has_field_values` is not `list`" + (is (= {:values [], :field_id (mt/id :venues :id)} + ((mt/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :venues :id)))))) + + (testing "Sensisitive fields do not have field values and should return empty" + (is (= {:values [], :field_id (mt/id :users :password)} + ((mt/user->client :rasta) :get 200 (format "field/%d/values" (mt/id :users :password)))))))) (def ^:private list-field {:name "Field Test", :base_type :type/Integer, :has_field_values "list"}) (deftest update-field-values-no-human-readable-values-test - (testing "Human readable values are optional" - (tt/with-temp* [Field [{field-id :id} list-field] - FieldValues [{field-value-id :id} {:values (range 5 10), :field_id field-id}]] - (testing "fetch initial values" - (is (= {:values [[5] [6] [7] [8] [9]], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) - (testing "update values" - (is (= {:status "success"} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :post 200 (format "field/%d/values" field-id) - {:values (map vector (range 1 5))}))))) - (testing "fetch updated values" - (is (= {:values [[1] [2] [3] [4]], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))))))) + (testing "POST /api/field/:id/values" + (testing "Human readable values are optional" + (mt/with-temp* [Field [{field-id :id} list-field] + FieldValues [{field-value-id :id} {:values (range 5 10), :field_id field-id}]] + (testing "fetch initial values" + (is (= {:values [[5] [6] [7] [8] [9]], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) + (testing "update values" + (is (= {:status "success"} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :post 200 (format "field/%d/values" field-id) + {:values (map vector (range 1 5))}))))) + (testing "fetch updated values" + (is (= {:values [[1] [2] [3] [4]], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))))))) (deftest update-field-values-with-human-readable-values-test - (testing "Existing field values can be updated (with their human readable values)" - (tt/with-temp* [Field [{field-id :id} list-field] - FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}]] - (testing "fetch initial values" - (is (= {:values [[1] [2] [3] [4]], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) - (testing "update values" - (is (= {:status "success"} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :post 200 (format "field/%d/values" field-id) - {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]]}))))) - (testing "fetch updated values" - (is (= {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))))))) + (testing "POST /api/field/:id/values" + (testing "Existing field values can be updated (with their human readable values)" + (mt/with-temp* [Field [{field-id :id} list-field] + FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}]] + (testing "fetch initial values" + (is (= {:values [[1] [2] [3] [4]], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) + (testing "update values" + (is (= {:status "success"} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :post 200 (format "field/%d/values" field-id) + {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]]}))))) + (testing "fetch updated values" + (is (= {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))))))) (deftest create-field-values-when-not-present-test - (testing "Field values should be created when not present" - ;; this will print an error message because it will try to fetch the FieldValues, but the Field doesn't - ;; exist; we can ignore that - (tu.log/suppress-output - (tt/with-temp Field [{field-id :id} list-field] - (is (= {:values [], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))) - - (is (= {:status "success"} - ((test-users/user->client :crowberto) :post 200 (format "field/%d/values" field-id) - {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]]}))) - - (is (= {:values [1 2 3 4], :human_readable_values ["$" "$$" "$$$" "$$$$"]} - (into {} (db/select-one [FieldValues :values :human_readable_values] :field_id field-id)))) - - (is (= {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} - (tu/boolean-ids-and-timestamps - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))))))) - -;; Can unset values -(expect - [{:values [[1] [2] [3] [4]], :field_id true} - {:status "success"} - {:values [], :field_id true}] - (tt/with-temp* [Field [{field-id :id} list-field] - FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}]] - (mapv tu/boolean-ids-and-timestamps - [((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id)) - ((test-users/user->client :crowberto) :post 200 (format "field/%d/values" field-id) - {:values [], :field_id true}) - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))]))) - -;; Can unset just human readable values -(expect - [{:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} - {:status "success"} - {:values [[1] [2] [3] [4]], :field_id true}] - (tt/with-temp* [Field [{field-id :id} list-field] - FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id - :human_readable_values ["$" "$$" "$$$" "$$$$"]}]] - (mapv tu/boolean-ids-and-timestamps - [((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id)) - ((test-users/user->client :crowberto) :post 200 (format "field/%d/values" field-id) - {:values [[1] [2] [3] [4]]}) - ((test-users/user->client :crowberto) :get 200 (format "field/%d/values" field-id))]))) - -;; Should throw when human readable values are present but not for every value -(expect - "If remapped values are specified, they must be specified for all field values" - (tt/with-temp* [Field [{field-id :id} {:name "Field Test", :base_type :type/Integer, :has_field_values "list"}]] - ((test-users/user->client :crowberto) :post 400 (format "field/%d/values" field-id) - {:values [[1 "$"] [2 "$$"] [3] [4]]}))) - -;; ## PUT /api/field/:id/dimension + (testing "POST /api/field/:id/values" + (testing "Field values should be created when not present" + ;; this will print an error message because it will try to fetch the FieldValues, but the Field doesn't + ;; exist; we can ignore that + (mt/suppress-output + (mt/with-temp Field [{field-id :id} list-field] + (is (= {:values [], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))) + + (is (= {:status "success"} + ((mt/user->client :crowberto) :post 200 (format "field/%d/values" field-id) + {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]]}))) + + (is (= {:values [1 2 3 4], :human_readable_values ["$" "$$" "$$$" "$$$$"]} + (into {} (db/select-one [FieldValues :values :human_readable_values] :field_id field-id)))) + + (is (= {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} + (mt/boolean-ids-and-timestamps + ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))))))) + +(deftest remove-field-values-test + (testing "POST /api/field/:id/values" + (mt/with-temp Field [{field-id :id} list-field] + (testing "should be able to unset FieldValues" + (mt/with-temp FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id}] + (testing "before updating values" + (is (= {:values [[1] [2] [3] [4]], :field_id true} + (mt/boolean-ids-and-timestamps ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) + (testing "API response" + (is (= {:status "success"} + ((mt/user->client :crowberto) :post 200 (format "field/%d/values" field-id) {:values [], :field_id true})))) + (testing "after updating values" + (is (= {:values [], :field_id true} + (mt/boolean-ids-and-timestamps ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))))[])) + + (testing "should be able to unset just the human-readable values" + (mt/with-temp FieldValues [{field-value-id :id} {:values (range 1 5), :field_id field-id + :human_readable_values ["$" "$$" "$$$" "$$$$"]}] + (testing "before updating values" + (is (= {:values [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]], :field_id true} + (mt/boolean-ids-and-timestamps ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id)))))) + (testing "API response" + (is (= {:status "success"} + ((mt/user->client :crowberto) :post 200 (format "field/%d/values" field-id) {:values [[1] [2] [3] [4]]})))) + (testing "after updating values" + (is (= {:values [[1] [2] [3] [4]], :field_id true} + (mt/boolean-ids-and-timestamps ((mt/user->client :crowberto) :get 200 (format "field/%d/values" field-id))))))))) + + (testing "attempting to updated values should throw when human readable values are present but not for every value" + (mt/with-temp Field [{field-id :id} {:name "Field Test", :base_type :type/Integer, :has_field_values "list"}] + (is (= "If remapped values are specified, they must be specified for all field values" + ((mt/user->client :crowberto) :post 400 (format "field/%d/values" field-id) + {:values [[1 "$"] [2 "$$"] [3] [4]]}))))))) (defn- dimension-for-field [field-id] (-> (Field :id field-id) @@ -296,10 +273,10 @@ {:style/indent 1} [field-id map-to-post & {:keys [expected-status-code] :or {expected-status-code 200}}] - ((test-users/user->client :crowberto) :post expected-status-code (format "field/%d/dimension" field-id) map-to-post)) + ((mt/user->client :crowberto) :post expected-status-code (format "field/%d/dimension" field-id) map-to-post)) (deftest create-update-dimension-test - (tt/with-temp* [Field [{field-id :id} {:name "Field Test"}]] + (mt/with-temp* [Field [{field-id :id} {:name "Field Test"}]] (testing "no dimension should exist for a new Field" (is (= [] (dimension-for-field field-id)))) @@ -313,7 +290,7 @@ :name "some dimension name" :human_readable_field_id false :field_id true} - (tu/boolean-ids-and-timestamps new-dim))) + (mt/boolean-ids-and-timestamps new-dim))) (testing "Update a Dimension" (create-dimension-via-API! field-id {:name "different dimension name", :type "internal"}) (let [updated-dim (dimension-for-field field-id)] @@ -324,7 +301,7 @@ :name "different dimension name" :human_readable_field_id false :field_id true} - (tu/boolean-ids-and-timestamps updated-dim))) + (mt/boolean-ids-and-timestamps updated-dim))) (testing "attempting to create a dimension when one already exists should update the existing" (is (= (u/get-id new-dim) (u/get-id updated-dim)))))))))) @@ -332,111 +309,119 @@ (deftest virtual-field-values-test (testing "Check that trying to get values for a 'virtual' field just returns a blank values map" (is (= {:values []} - ((test-users/user->client :rasta) :get 200 (format "field/%s/values" (codec/url-encode "field-literal,created_at,type/Datetime"))))))) + ((mt/user->client :rasta) :get 200 (format "field/%s/values" (codec/url-encode "field-literal,created_at,type/Datetime"))))))) (deftest create-dimension-with-human-readable-field-id-test - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] - Field [{field-id-2 :id} {:name "Field Test 2"}]] - (testing "before creation" - (is (= [] - (dimension-for-field field-id-1)))) - (create-dimension-via-API! field-id-1 - {:name "some dimension name", :type "external" :human_readable_field_id field-id-2}) - (testing "after creation" - (is (= {:id true - :created_at true - :updated_at true - :type :external - :name "some dimension name" - :human_readable_field_id true - :field_id true} - (tu/boolean-ids-and-timestamps (dimension-for-field field-id-1))))))) - -;; External remappings require a human readable field id -(expect - "Foreign key based remappings require a human readable field id" - (tt/with-temp* [Field [{field-id :id} {:name "Field Test 1"}]] - (create-dimension-via-API! field-id - {:name "some dimension name", :type "external"} - :expected-status-code 400))) - -;; Non-admin users can't update dimension, :field_id trues -(expect - "You don't have permissions to do that." - (tt/with-temp* [Field [{field-id :id} {:name "Field Test 1"}]] - ((test-users/user->client :rasta) :post 403 (format "field/%d/dimension" field-id) - {:name "some dimension name", :type "external"}))) - -(deftest delete-dimension-test - (testing "Ensure we can delete a dimension" - (tt/with-temp Field [{field-id :id} {:name "Field Test"}] - (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) - (testing "before deletion" - (is (= {:id true - :created_at true - :updated_at true - :type :internal - :name "some dimension name" - :human_readable_field_id false - :field_id true} - (mt/boolean-ids-and-timestamps (dimension-for-field field-id))))) - ((test-users/user->client :crowberto) :delete 204 (format "field/%d/dimension" field-id)) - (testing "after deletion" - (is (= [] - (dimension-for-field field-id))))))) - -(deftest delete-dimension-permissions-test - (testing "Non-admin users can't delete a dimension" - (tt/with-temp Field [{field-id :id} {:name "Field Test 1"}] - (is (= "You don't have permissions to do that." - ((test-users/user->client :rasta) :delete 403 (format "field/%d/dimension" field-id))))))) - -(deftest clear-exetrnal-dimension-when-fk-special-type-is-removed-test - (testing "When an FK field gets it's special_type removed, we should clear the external dimension" - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1" - :special_type :type/FK}] + (testing "POST /api/field/:id/dimension" + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] Field [{field-id-2 :id} {:name "Field Test 2"}]] + (testing "before creation" + (is (= [] + (dimension-for-field field-id-1)))) (create-dimension-via-API! field-id-1 - {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2}) - (testing "before update" + {:name "some dimension name", :type "external" :human_readable_field_id field-id-2}) + (testing "after creation" (is (= {:id true :created_at true :updated_at true :type :external - :name "fk-remove-dimension" + :name "some dimension name" :human_readable_field_id true :field_id true} - (tu/boolean-ids-and-timestamps (dimension-for-field field-id-1))))) - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-1) {:special_type nil}) - (testing "after update" - (is (= [] - (tu/boolean-ids-and-timestamps (dimension-for-field field-id-1)))))))) - -(expect - (repeat 2 {:id true - :created_at true - :updated_at true - :type :external - :name "fk-remove-dimension" - :human_readable_field_id true - :field_id true}) - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1" - :special_type :type/FK}] - Field [{field-id-2 :id} {:name "Field Test 2"}]] - ;; create the Dimension - (create-dimension-via-API! field-id-1 - {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2}) - ;; now change something unrelated: description - (let [new-dim (dimension-for-field field-id-1) - _ ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-1) - {:description "something diffrent"}) - dim-after-update (dimension-for-field field-id-1)] - [(tu/boolean-ids-and-timestamps new-dim) - (tu/boolean-ids-and-timestamps dim-after-update)]))) + (mt/boolean-ids-and-timestamps (dimension-for-field field-id-1)))))))) + +(deftest create-dimension-validation-test + (testing "POST /api/field/:id/dimension" + (testing "External remappings require a human readable field id" + (mt/with-temp Field [{field-id :id} {:name "Field Test 1"}] + (is (= "Foreign key based remappings require a human readable field id" + (create-dimension-via-API! field-id + {:name "some dimension name", :type "external"} + :expected-status-code 400))))) + + (testing "Non-admin users can't update dimension" + (mt/with-temp Field [{field-id :id} {:name "Field Test 1"}] + (is (= "You don't have permissions to do that." + ((mt/user->client :rasta) :post 403 (format "field/%d/dimension" field-id) + {:name "some dimension name", :type "external"}))))))) + +(deftest delete-dimension-test + (testing "DELETE /api/field/:id/dimension" + (testing "Ensure we can delete a dimension" + (mt/with-temp Field [{field-id :id} {:name "Field Test"}] + (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) + (testing "before deletion" + (is (= {:id true + :created_at true + :updated_at true + :type :internal + :name "some dimension name" + :human_readable_field_id false + :field_id true} + (mt/boolean-ids-and-timestamps (dimension-for-field field-id))))) + ((mt/user->client :crowberto) :delete 204 (format "field/%d/dimension" field-id)) + (testing "after deletion" + (is (= [] + (dimension-for-field field-id)))))))) + +(deftest delete-dimension-permissions-test + (testing "DELETE /api/field/:id/dimension" + (testing "Non-admin users can't delete a dimension" + (mt/with-temp Field [{field-id :id} {:name "Field Test 1"}] + (is (= "You don't have permissions to do that." + ((mt/user->client :rasta) :delete 403 (format "field/%d/dimension" field-id)))))))) + +(deftest clear-external-dimension-when-fk-special-type-is-removed-test + (testing "PUT /api/field/:id" + (testing "When an FK field gets it's special_type removed, we should clear the external dimension" + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1" + :special_type :type/FK}] + Field [{field-id-2 :id} {:name "Field Test 2"}]] + (create-dimension-via-API! field-id-1 + {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2}) + (testing "before update" + (is (= {:id true + :created_at true + :updated_at true + :type :external + :name "fk-remove-dimension" + :human_readable_field_id true + :field_id true} + (mt/boolean-ids-and-timestamps (dimension-for-field field-id-1))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-1) {:special_type nil}) + (testing "after update" + (is (= [] + (mt/boolean-ids-and-timestamps (dimension-for-field field-id-1))))))))) + +(deftest update-field-should-not-affect-dimensions-test + (testing "PUT /api/field/:id" + (testing "Updating unrelated properties should not affect a Field's `:dimensions`" + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1" + :special_type :type/FK}] + Field [{field-id-2 :id} {:name "Field Test 2"}]] + ;; create the Dimension + (create-dimension-via-API! field-id-1 + {:name "fk-remove-dimension", :type "external" :human_readable_field_id field-id-2}) + (let [expected {:id true + :created_at true + :updated_at true + :type :external + :name "fk-remove-dimension" + :human_readable_field_id true + :field_id true}] + (testing "before API request" + (is (= expected + (mt/boolean-ids-and-timestamps (dimension-for-field field-id-1))))) + ;; now change something unrelated: description + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-1) + {:description "something diffrent"}) + (testing "after API request" + (is (= expected + (mt/boolean-ids-and-timestamps (dimension-for-field field-id-1)))))))))) (deftest remove-fk-special-type-test (testing "When removing the FK special type, the fk_target_field_id should be cleared as well" - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] Field [{field-id-2 :id} {:name "Field Test 2" :special_type :type/FK :fk_target_field_id field-id-1}]] @@ -447,8 +432,8 @@ :visibility_type :normal :special_type :type/FK :fk_target_field_id true} - (tu/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:special_type nil}) + (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:special_type nil}) (testing "after change" (is (= {:name "Field Test 2" :display_name "Field Test 2" @@ -456,11 +441,11 @@ :visibility_type :normal :special_type nil :fk_target_field_id false} - (tu/boolean-ids-and-timestamps (simple-field-details (Field field-id-2))))))))) + (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2))))))))) (deftest update-fk-target-field-id-test (testing "Checking update of the fk_target_field_id" - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] Field [{field-id-2 :id} {:name "Field Test 2"}] Field [{field-id-3 :id} {:name "Field Test 3" :special_type :type/FK @@ -473,8 +458,8 @@ :visibility_type :normal :special_type :type/FK :fk_target_field_id true} - (tu/boolean-ids-and-timestamps before-change)))) - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-3) {:fk_target_field_id field-id-2}) + (mt/boolean-ids-and-timestamps before-change)))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-3) {:fk_target_field_id field-id-2}) (testing "after change" (let [after-change (simple-field-details (Field field-id-3))] (is (= {:name "Field Test 3" @@ -483,13 +468,13 @@ :visibility_type :normal :special_type :type/FK :fk_target_field_id true} - (tu/boolean-ids-and-timestamps after-change))) + (mt/boolean-ids-and-timestamps after-change))) (is (not= (:fk_target_field_id before-change) (:fk_target_field_id after-change))))))))) (deftest update-fk-target-field-id-with-fk-test (testing "Checking update of the fk_target_field_id along with an FK change" - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] Field [{field-id-2 :id} {:name "Field Test 2"}]] (testing "before change" @@ -499,8 +484,8 @@ :visibility_type :normal :special_type nil :fk_target_field_id false} - (tu/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:special_type :type/FK + (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:special_type :type/FK :fk_target_field_id field-id-1}) (testing "after change" (is (= {:name "Field Test 2" @@ -509,73 +494,78 @@ :visibility_type :normal :special_type :type/FK :fk_target_field_id true} - (tu/boolean-ids-and-timestamps (simple-field-details (Field field-id-2))))))))) - -(deftest fk-target-field-id-shouldnt-change-test - (testing "fk_target_field_id and FK should remain unchanged on updates of other fields" - (tt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] - Field [{field-id-2 :id} {:name "Field Test 2" - :special_type :type/FK - :fk_target_field_id field-id-1}]] - (testing "before change" - (is (= {:name "Field Test 2" - :display_name "Field Test 2" - :description nil - :visibility_type :normal - :special_type :type/FK - :fk_target_field_id true} - (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:description "foo"}) - (testing "after change" - (is (= {:name "Field Test 2" - :display_name "Field Test 2" - :description "foo" - :visibility_type :normal - :special_type :type/FK - :fk_target_field_id true} (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2))))))))) -;; Changing a remapped field's type to something that can't be remapped will clear the dimension -(expect - [{:id true - :created_at true - :updated_at true - :type :internal - :name "some dimension name" - :human_readable_field_id false - :field_id true} - []] - (tt/with-temp Field [{field-id :id} {:name "Field Test" - :base_type "type/Integer"}] - (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) - (let [new-dim (dimension-for-field field-id)] - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type "type/Text"}) - [(tu/boolean-ids-and-timestamps new-dim) - (dimension-for-field field-id)]))) - -;; Change from supported type to supported type will leave the dimension -(expect - (repeat 2 {:id true - :created_at true - :updated_at true - :type :internal - :name "some dimension name" - :human_readable_field_id false - :field_id true}) - (tt/with-temp Field [{field-id :id} {:name "Field Test" - :base_type "type/Integer"}] - (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) - (let [new-dim (dimension-for-field field-id)] - ((test-users/user->client :crowberto) :put 200 (format "field/%d" field-id) {:has_field_values "list"}) - [(tu/boolean-ids-and-timestamps new-dim) - (tu/boolean-ids-and-timestamps (dimension-for-field field-id))]))) +(deftest fk-target-field-id-shouldnt-change-test + (testing "PUT /api/field/:id" + (testing "fk_target_field_id and FK should remain unchanged on updates of other fields" + (mt/with-temp* [Field [{field-id-1 :id} {:name "Field Test 1"}] + Field [{field-id-2 :id} {:name "Field Test 2" + :special_type :type/FK + :fk_target_field_id field-id-1}]] + (testing "before change" + (is (= {:name "Field Test 2" + :display_name "Field Test 2" + :description nil + :visibility_type :normal + :special_type :type/FK + :fk_target_field_id true} + (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id-2) {:description "foo"}) + (testing "after change" + (is (= {:name "Field Test 2" + :display_name "Field Test 2" + :description "foo" + :visibility_type :normal + :special_type :type/FK + :fk_target_field_id true} + (mt/boolean-ids-and-timestamps (simple-field-details (Field field-id-2)))))))))) + +(deftest update-field-type-dimension-test + (testing "PUT /api/field/:id" + (testing "Changing a remapped field's type to something that can't be remapped will clear the dimension" + (mt/with-temp Field [{field-id :id} {:name "Field Test" + :base_type "type/Integer"}] + (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) + (testing "before API request" + (is (= {:id true + :created_at true + :updated_at true + :type :internal + :name "some dimension name" + :human_readable_field_id false + :field_id true} + (mt/boolean-ids-and-timestamps (dimension-for-field field-id))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type "type/Text"}) + (testing "after API request" + (is (= [] + (dimension-for-field field-id)))))) + + (testing "Change from supported type to supported type will leave the dimension" + (mt/with-temp Field [{field-id :id} {:name "Field Test" + :base_type "type/Integer"}] + (create-dimension-via-API! field-id {:name "some dimension name", :type "internal"}) + (let [expected {:id true + :created_at true + :updated_at true + :type :internal + :name "some dimension name" + :human_readable_field_id false + :field_id true}] + (testing "before API request" + (is (= expected + (mt/boolean-ids-and-timestamps (dimension-for-field field-id))))) + ((mt/user->client :crowberto) :put 200 (format "field/%d" field-id) {:has_field_values "list"}) + (testing "after API request" + (is (= expected + (mt/boolean-ids-and-timestamps (dimension-for-field field-id)))))))))) (deftest update-field-settings-test (testing "Can we update Field.settings, and fetch it?" - (tt/with-temp Field [field {:name "Crissy Field"}] - ((test-users/user->client :crowberto) :put 200 (format "field/%d" (u/get-id field)) {:settings {:field_is_cool true}}) + (mt/with-temp Field [field {:name "Crissy Field"}] + ((mt/user->client :crowberto) :put 200 (format "field/%d" (u/get-id field)) {:settings {:field_is_cool true}}) (is (= {:field_is_cool true} - (-> ((test-users/user->client :crowberto) :get 200 (format "field/%d" (u/get-id field))) + (-> ((mt/user->client :crowberto) :get 200 (format "field/%d" (u/get-id field))) :settings)))))) (deftest search-values-test diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj index bf35cb1ae2a9163675f9b0feb8156ea52ae1205c..1eb1a4083f520b0e8cc97d2d42c10cfb236787ce 100644 --- a/test/metabase/api/table_test.clj +++ b/test/metabase/api/table_test.clj @@ -1,6 +1,7 @@ (ns metabase.api.table-test "Tests for /api/table endpoints." - (:require [clojure + (:require [cheshire.core :as json] + [clojure [test :refer :all] [walk :as walk]] [expectations :refer [expect]] @@ -65,12 +66,15 @@ :active true :db_id (mt/id) :segments [] - :metrics []}) + :metrics [] + :field_order "database"}) (def ^:private field-defaults {:description nil :active true :position 0 + :database_position 0 + :custom_position 0 :target nil :preview_display true :visibility_type "normal" @@ -182,7 +186,9 @@ :base_type "type/Text" :dimension_options [] :default_dimension_option nil - :has_field_values "list"})] + :has_field_values "list" + :database_position 1 + :position 1})] :rows nil :id (mt/id :categories)}) ((mt/user->client :rasta) :get 200 (format "table/%d/query_metadata" (mt/id :categories)))) @@ -208,16 +214,6 @@ :base_type "type/BigInteger" :visibility_type "normal" :has_field_values "none") - (assoc (field-details (Field (mt/id :users :last_login))) - :table_id (mt/id :users) - :name "LAST_LOGIN" - :display_name "Last Login" - :database_type "TIMESTAMP" - :base_type "type/DateTime" - :visibility_type "normal" - :dimension_options (var-get #'table-api/datetime-dimension-indexes) - :default_dimension_option (var-get #'table-api/date-default-index) - :has_field_values "none") (assoc (field-details (Field (mt/id :users :name))) :special_type "type/Name" :table_id (mt/id :users) @@ -228,7 +224,21 @@ :visibility_type "normal" :dimension_options [] :default_dimension_option nil - :has_field_values "list") + :has_field_values "list" + :position 1 + :database_position 1) + (assoc (field-details (Field (mt/id :users :last_login))) + :table_id (mt/id :users) + :name "LAST_LOGIN" + :display_name "Last Login" + :database_type "TIMESTAMP" + :base_type "type/DateTime" + :visibility_type "normal" + :dimension_options (var-get #'table-api/datetime-dimension-indexes) + :default_dimension_option (var-get #'table-api/date-default-index) + :has_field_values "none" + :position 2 + :database_position 2) (assoc (field-details (Field :table_id (mt/id :users), :name "PASSWORD")) :special_type "type/Category" :table_id (mt/id :users) @@ -237,7 +247,9 @@ :database_type "VARCHAR" :base_type "type/Text" :visibility_type "sensitive" - :has_field_values "list")] + :has_field_values "list" + :position 3 + :database_position 3)] :rows nil :id (mt/id :users)}) ((test-users/user->client :rasta) :get 200 (format "table/%d/query_metadata?include_sensitive_fields=true" (mt/id :users))))))) @@ -261,6 +273,16 @@ :database_type "BIGINT" :base_type "type/BigInteger" :has_field_values "none") + (assoc (field-details (Field (mt/id :users :name))) + :table_id (mt/id :users) + :special_type "type/Name" + :name "NAME" + :display_name "Name" + :database_type "VARCHAR" + :base_type "type/Text" + :has_field_values "list" + :position 1 + :database_position 1) (assoc (field-details (Field (mt/id :users :last_login))) :table_id (mt/id :users) :name "LAST_LOGIN" @@ -269,15 +291,9 @@ :base_type "type/DateTime" :dimension_options (var-get #'table-api/datetime-dimension-indexes) :default_dimension_option (var-get #'table-api/date-default-index) - :has_field_values "none") - (assoc (field-details (Field (mt/id :users :name))) - :table_id (mt/id :users) - :special_type "type/Name" - :name "NAME" - :display_name "Name" - :database_type "VARCHAR" - :base_type "type/Text" - :has_field_values "list")] + :has_field_values "none" + :position 2 + :database_position 2)] :rows nil :id (mt/id :users)}) ((test-users/user->client :rasta) :get 200 (format "table/%d/query_metadata" (mt/id :users))))))) @@ -368,6 +384,8 @@ :database_type "INTEGER" :base_type "type/Integer" :special_type "type/FK" + :database_position 2 + :position 2 :table (merge (dissoc (table-defaults) :segments :field_values :metrics) (db/select-one [Table :id :created_at :updated_at :fields_hash] @@ -717,3 +735,35 @@ (testing "For tables that don't exist, we should return a 404." (is (= "Not found." ((mt/user->client :crowberto) :post 404 (format "table/%d/discard_values" Integer/MAX_VALUE))))))) + +(deftest field-ordering-test + (let [original-field-order (db/select-one-field :field_order Table :id (mt/id :venues))] + (try + (testing "Cane we set alphabetical field ordering?" + (is (= ["CATEGORY_ID" "ID" "LATITUDE" "LONGITUDE" "NAME" "PRICE"] + (->> ((mt/user->client :crowberto) :put 200 (format "table/%s" (mt/id :venues)) + {:field_order :alphabetical}) + :fields + (map :name))))) + (testing "Cane we set smart field ordering?" + (is (= ["ID" "NAME" "CATEGORY_ID" "LATITUDE" "LONGITUDE" "PRICE"] + (->> ((mt/user->client :crowberto) :put 200 (format "table/%s" (mt/id :venues)) + {:field_order :smart}) + :fields + (map :name))))) + (testing "Cane we set database field ordering?" + (is (= ["ID" "NAME" "CATEGORY_ID" "LATITUDE" "LONGITUDE" "PRICE"] + (->> ((mt/user->client :crowberto) :put 200 (format "table/%s" (mt/id :venues)) + {:field_order :database}) + :fields + (map :name))))) + (testing "Cane we set custom field ordering?" + (let [custom-field-order [(mt/id :venues :price) (mt/id :venues :longitude) (mt/id :venues :id) + (mt/id :venues :category_id) (mt/id :venues :name) (mt/id :venues :latitude)]] + ((mt/user->client :crowberto) :put 200 (format "table/%s/fields/order" (mt/id :venues)) + {:request-options {:body (json/encode custom-field-order)}}) + (is (= custom-field-order + (->> (table/fields (Table (mt/id :venues))) + (map u/get-id)))))) + (finally ((mt/user->client :crowberto) :put 200 (format "table/%s" (mt/id :venues)) + {:field_order original-field-order}))))) diff --git a/test/metabase/api/transform_test.clj b/test/metabase/api/transform_test.clj index d48e8de859b93b9c4fd9dbb591b1915704a5c19b..c1e25779b25204ec4d62f0706f02ae5fee2df6c9 100644 --- a/test/metabase/api/transform_test.clj +++ b/test/metabase/api/transform_test.clj @@ -23,9 +23,9 @@ ;; Run the transform and make sure it produces the correct result (expect - [[4 1 10.0646 -165.374 "Red Medicine" 3 1.5 4 3 2 1] - [11 2 34.0996 -118.329 "Stout Burgers & Beers" 2 2.0 11 2 1 1] - [11 3 34.0406 -118.428 "The Apple Pan" 2 2.0 11 2 1 1]] + [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5 4 3 2 1] + [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0 11 2 1 1] + [3 "The Apple Pan" 11 34.0406 -118.428 2 2.0 11 2 1 1]] (test-users/with-test-user :rasta (with-test-transform-specs (with-test-domain-entity-specs @@ -37,12 +37,12 @@ :data :rows)))))) -;; Do we correctly check for permissions? -(expect - (try - (do - (perms/revoke-permissions! (perms-group/all-users) (data/id)) - (= ((test-users/user->client :rasta) :get 403 (test-endpoint)) - "You don't have permissions to do that.")) - (finally - (perms/grant-permissions! (perms-group/all-users) (perms/object-path (data/id)))))) +(deftest permissions-test + (testing "GET /api/transform/:db-id/:schema/:transform-name" + (testing "Do we correctly check for permissions?" + (try + (perms/revoke-permissions! (perms-group/all-users) (data/id)) + (is (= "You don't have permissions to do that." + ((test-users/user->client :rasta) :get 403 (test-endpoint)))) + (finally + (perms/grant-permissions! (perms-group/all-users) (perms/object-path (data/id)))))))) diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index 068559cf8a8995fe2f18c7bbaa2c1dbaea32d23c..039acfa8713c7b2df87b6de2286c809ce78c57b2 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -247,7 +247,7 @@ (mt/rows (mt/run-mbql-query users {:filter [:= $user_id nil]}))))) (testing "Check that we can filter by a UUID for SQL Field filters (#7955)" - (is (= [[#uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027" 1]] + (is (= [[1 #uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027"]] (mt/rows (qp/process-query (assoc (mt/native-query @@ -262,8 +262,8 @@ :target ["dimension" ["template-tag" "user"]] :value "4f01dcfd-13f7-430c-8e6f-e505c0851027"}]))))) (testing "Check that we can filter by multiple UUIDs for SQL Field filters" - (is (= [[#uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027" 1] - [#uuid "da1d6ecc-e775-4008-b366-c38e7a2e8433" 3]] + (is (= [[1 #uuid "4f01dcfd-13f7-430c-8e6f-e505c0851027"] + [3 #uuid "da1d6ecc-e775-4008-b366-c38e7a2e8433"]] (mt/rows (qp/process-query (assoc (mt/native-query @@ -357,13 +357,13 @@ "CREATE TYPE bird_status AS ENUM ('good bird', 'angry bird', 'delicious bird');" (str "CREATE TABLE birds (" " name varchar PRIMARY KEY NOT NULL," - " type \"bird type\" NOT NULL," - " status bird_status NOT NULL" + " status bird_status NOT NULL," + " type \"bird type\" NOT NULL" ");") - (str "INSERT INTO birds (\"name\", \"type\", status) VALUES" - " ('Rasta', 'toucan', 'good bird')," - " ('Lucky', 'pigeon', 'angry bird')," - " ('Theodore', 'turkey', 'delicious bird');")]] + (str "INSERT INTO birds (\"name\", status, \"type\") VALUES" + " ('Rasta', 'good bird', 'toucan')," + " ('Lucky', 'angry bird', 'pigeon')," + " ('Theodore', 'delicious bird', 'turkey');")]] (jdbc/execute! conn [sql])))) (defn- do-with-enums-db {:style/indent 0} [f] @@ -386,16 +386,19 @@ (testing "check that describe-table properly describes the database & base types of the enum fields" (is (= {:name "birds" - :fields #{{:name "name", - :database-type "varchar" - :base-type :type/Text - :pk? true} - {:name "status" - :database-type "bird_status" - :base-type :type/PostgresEnum} - {:name "type" - :database-type "bird type" - :base-type :type/PostgresEnum}}} + :fields #{{:name "name" + :database-type "varchar" + :base-type :type/Text + :pk? true + :database-position 0} + {:name "status" + :database-type "bird_status" + :base-type :type/PostgresEnum + :database-position 1} + {:name "type" + :database-type "bird type" + :base-type :type/PostgresEnum + :database-position 2}}} (driver/describe-table :postgres db {:name "birds"})))) (testing "check that when syncing the DB the enum types get recorded appropriately" diff --git a/test/metabase/driver/sql_jdbc/sync_test.clj b/test/metabase/driver/sql_jdbc/sync_test.clj index ef0f871b76bba4ac70c538361f9b160152a0efba..aac5bd961307d3d288a140ab9b6be3d03215647b 100644 --- a/test/metabase/driver/sql_jdbc/sync_test.clj +++ b/test/metabase/driver/sql_jdbc/sync_test.clj @@ -1,11 +1,12 @@ (ns metabase.driver.sql-jdbc.sync-test (:require [clojure.java.jdbc :as jdbc] - [metabase.driver :as driver] + [clojure.test :refer :all] + [metabase + [driver :as driver] + [test :as mt]] [metabase.driver.sql-jdbc [connection :as sql-jdbc.conn] - [sync :as sql-jdbc.sync]] - [metabase.test.data :as data] - [metabase.test.data.datasets :as datasets]) + [sync :as sql-jdbc.sync]]) (:import java.sql.ResultSet)) (defn- sql-jdbc-drivers-with-default-describe-database-impl @@ -34,11 +35,12 @@ ;; they would normally get closed (jdbc/with-db-connection [conn (sql-jdbc.conn/db->pooled-connection-spec db)] (sql-jdbc.sync/describe-database driver conn) - (reduce + (for [rs @resultsets] + (reduce + (for [^ResultSet rs @resultsets] (if (.isClosed rs) 0 1))))))) -;; make sure that running the sync process doesn't leak cursors because it's not closing the ResultSets -;; See issues #4389, #6028, and #6467 (Oracle) and #7609 (Redshift) -(datasets/expect-with-drivers (sql-jdbc-drivers-with-default-describe-database-impl) - 0 - (describe-database-with-open-resultset-count driver/*driver* (data/db))) +(deftest dont-leak-resultsets-test + (mt/test-drivers (sql-jdbc-drivers-with-default-describe-database-impl) + (testing (str "make sure that running the sync process doesn't leak cursors because it's not closing the ResultSets. " + "See issues #4389, #6028, and #6467 (Oracle) and #7609 (Redshift)") + (is (= 0 + (describe-database-with-open-resultset-count driver/*driver* (mt/db))))))) diff --git a/test/metabase/driver/sql_jdbc_test.clj b/test/metabase/driver/sql_jdbc_test.clj index 5c6fc411aefa9fb2be588fb29c2250c0d3bcefe7..4b8edee8873509535b996b9a2de3c81c00e1a853 100644 --- a/test/metabase/driver/sql_jdbc_test.clj +++ b/test/metabase/driver/sql_jdbc_test.clj @@ -20,25 +20,31 @@ (deftest describe-table-test (is (= {:name "VENUES" :schema "PUBLIC" - :fields #{{:name "NAME", - :database-type "VARCHAR" - :base-type :type/Text} - {:name "LATITUDE" - :database-type "DOUBLE" - :base-type :type/Float} - {:name "LONGITUDE" - :database-type "DOUBLE" - :base-type :type/Float} - {:name "PRICE" - :database-type "INTEGER" - :base-type :type/Integer} - {:name "CATEGORY_ID" - :database-type "INTEGER" - :base-type :type/Integer} - {:name "ID" - :database-type "BIGINT" - :base-type :type/BigInteger - :pk? true}}} + :fields #{{:name "ID" + :database-type "BIGINT" + :base-type :type/BigInteger + :pk? true + :database-position 0} + {:name "NAME" + :database-type "VARCHAR" + :base-type :type/Text + :database-position 1} + {:name "CATEGORY_ID" + :database-type "INTEGER" + :base-type :type/Integer + :database-position 2} + {:name "LATITUDE" + :database-type "DOUBLE" + :base-type :type/Float + :database-position 3} + {:name "LONGITUDE" + :database-type "DOUBLE" + :base-type :type/Float + :database-position 4} + {:name "PRICE" + :database-type "INTEGER" + :base-type :type/Integer + :database-position 5}}} (driver/describe-table :h2 (mt/db) (Table (mt/id :venues)))))) (deftest describe-table-fks-test diff --git a/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj b/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj index 608f4123f338ee8ed19513a663caab55daea93dc..14300c61da194b182fc6b35954e7b589ebdd132a 100644 --- a/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj +++ b/test/metabase/query_processor/middleware/add_implicit_clauses_test.clj @@ -19,11 +19,11 @@ ;; PK {:position 0, :name "ID", :special_type :type/PK} ;; Name - {:position 0, :name "NAME", :special_type :type/Name} + {:position 1, :name "NAME", :special_type :type/Name} ;; The rest are sorted by name - {:position 0, :name "CATEGORY_ID", :special_type :type/FK} - {:position 0, :name "LATITUDE", :special_type :type/Latitude} - {:position 0, :name "LONGITUDE", :special_type :type/Longitude}] + {:position 2, :name "CATEGORY_ID", :special_type :type/FK} + {:position 3, :name "LATITUDE", :special_type :type/Latitude} + {:position 4, :name "LONGITUDE", :special_type :type/Longitude}] (tu/with-temp-vals-in-db Field (data/id :venues :price) {:position -1} (let [ids (map second (#'add-implicit-clauses/sorted-implicit-fields-for-table (data/id :venues))) id->field (u/key-by :id (db/select [Field :id :position :name :special_type] :id [:in ids]))] @@ -91,20 +91,20 @@ (#'add-implicit-clauses/add-implicit-fields (:query (data/mbql-query venues)))) ;; when adding sorted implicit Fields, Field positions should be taken into account -(tt/expect-with-temp [Field [field-1 {:table_id (data/id :venues), :position 1, :name "bbbbb"}] - Field [field-2 {:table_id (data/id :venues), :position 2, :name "aaaaa"}]] +(tt/expect-with-temp [Field [field-1 {:table_id (data/id :venues), :position 100, :name "bbbbb"}] + Field [field-2 {:table_id (data/id :venues), :position 101, :name "aaaaa"}]] (:query (data/mbql-query venues - {:fields [ ;; all fields with position = 0 should get sorted first according to rules above + {:fields [ ;; all fields with lower positions should get sorted first according to rules above $id $name $category_id $latitude $longitude $price - ;; followed by position = 1, then position = 2 + ;; followed by position = 100, then position = 101 [:field-id (u/get-id field-1)] [:field-id (u/get-id field-2)]]})) (#'add-implicit-clauses/add-implicit-fields (:query (data/mbql-query venues)))) (deftest default-bucketing-test (testing "datetime Fields should get default bucketing of :day" - (tt/with-temp* [Field [field {:table_id (data/id :venues), :position 0, :name "aaaaa", :base_type :type/DateTime}]] + (tt/with-temp* [Field [field {:table_id (data/id :venues), :position 2, :name "aaaaa", :base_type :type/DateTime}]] (is (= (:query (data/mbql-query venues {:fields [[:field-id (data/id :venues :id)] diff --git a/test/metabase/query_processor_test.clj b/test/metabase/query_processor_test.clj index c928d4b603d51f472721d67d7f7f5847c0e5a7f0..a782c088df1b07ce74e687e2188d5d1c9ce5fee3 100644 --- a/test/metabase/query_processor_test.clj +++ b/test/metabase/query_processor_test.clj @@ -220,18 +220,22 @@ (defmethod format-rows-fns :categories [_] + ;; ID NAME [int identity]) (defmethod format-rows-fns :checkins [_] + ;; ID DATE USER_ID VENUE_ID [int identity int int]) (defmethod format-rows-fns :users [_] + ;; ID NAME LAST_LOGIN [int identity identity]) (defmethod format-rows-fns :venues [_] + ;; ID NAME CATEGORY_ID LATITUDE LONGITUDE PRICE [int identity int 4.0 4.0 int]) (defn- format-rows-fn diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj index 65c6be4d0bc2657f20c436bb0d99f4c9db8614e2..153356bdf7ab93a99176a681b7777d55167c0421 100644 --- a/test/metabase/query_processor_test/breakout_test.clj +++ b/test/metabase/query_processor_test/breakout_test.clj @@ -15,80 +15,74 @@ [add-dimension-projections :as add-dim-projections] [add-source-metadata :as add-source-metadata]] [metabase.query-processor.test-util :as qp.test-util] - [metabase.test - [data :as data] - [util :as tu]] - [metabase.test.data.datasets :as datasets] - [metabase.test.util.log :as tu.log] - [toucan.db :as db] - [toucan.util.test :as tt])) + [metabase.test.data :as data] + [toucan.db :as db])) -;;; single column -(qp.test/expect-with-non-timeseries-dbs - {:rows [[1 31] [2 70] [3 75] [4 77] [5 69] [6 70] [7 76] [8 81] [9 68] [10 78] [11 74] [12 59] [13 76] [14 62] [15 34]] - :cols [(qp.test/breakout-col :checkins :user_id) - (qp.test/aggregate-col :count)]} - (qp.test/rows-and-cols - (qp.test/format-rows-by [int int] - (data/run-mbql-query checkins - {:aggregation [[:count]] - :breakout [$user_id] - :order-by [[:asc $user_id]]})))) - -;;; BREAKOUT w/o AGGREGATION -;; This should act as a "distinct values" query and return ordered results -(qp.test/expect-with-non-timeseries-dbs - {:cols [(qp.test/breakout-col :checkins :user_id)] - :rows [[1] [2] [3] [4] [5] [6] [7] [8] [9] [10]]} - (qp.test/rows-and-cols - (qp.test/format-rows-by [int] - (data/run-mbql-query checkins - {:breakout [$user_id] - :limit 10})))) +(deftest basic-test + (mt/test-drivers (mt/normal-drivers) + (testing "single column" + (testing "with breakout" + (is (= {:rows [[1 31] [2 70] [3 75] [4 77] [5 69] [6 70] [7 76] [8 81] [9 68] [10 78] [11 74] [12 59] [13 76] [14 62] [15 34]] + :cols [(qp.test/breakout-col :checkins :user_id) + (qp.test/aggregate-col :count)]} + (qp.test/rows-and-cols + (mt/format-rows-by [int int] + (mt/run-mbql-query checkins + {:aggregation [[:count]] + :breakout [$user_id] + :order-by [[:asc $user_id]]})))))) + (testing "without breakout" + (testing "This should act as a \"distinct values\" query and return ordered results" + (is (= {:cols [(qp.test/breakout-col :checkins :user_id)] + :rows [[1] [2] [3] [4] [5] [6] [7] [8] [9] [10]]} + (qp.test/rows-and-cols + (mt/format-rows-by [int] + (mt/run-mbql-query checkins + {:breakout [$user_id] + :limit 10})))))))) -;;; "BREAKOUT" - MULTIPLE COLUMNS W/ IMPLICT "ORDER_BY" -;; Fields should be implicitly ordered :ASC for all the fields in `breakout` that are not specified in `order-by` -(qp.test/expect-with-non-timeseries-dbs - {:rows [[1 1 1] [1 5 1] [1 7 1] [1 10 1] [1 13 1] [1 16 1] [1 26 1] [1 31 1] [1 35 1] [1 36 1]] - :cols [(qp.test/breakout-col :checkins :user_id) - (qp.test/breakout-col :checkins :venue_id) - (qp.test/aggregate-col :count)]} - (qp.test/rows-and-cols - (qp.test/format-rows-by [int int int] - (data/run-mbql-query checkins - {:aggregation [[:count]] - :breakout [$user_id $venue_id] - :limit 10})))) + (testing "multiple columns" + (testing "without explicit order by" + (testing "Fields should be implicitly ordered :ASC for all the fields in `breakout` that are not specified in `order-by`" + (is (= {:rows [[1 1 1] [1 5 1] [1 7 1] [1 10 1] [1 13 1] [1 16 1] [1 26 1] [1 31 1] [1 35 1] [1 36 1]] + :cols [(qp.test/breakout-col :checkins :user_id) + (qp.test/breakout-col :checkins :venue_id) + (qp.test/aggregate-col :count)]} + (qp.test/rows-and-cols + (mt/format-rows-by [int int int] + (mt/run-mbql-query checkins + {:aggregation [[:count]] + :breakout [$user_id $venue_id] + :limit 10}))))))) -;;; "BREAKOUT" - MULTIPLE COLUMNS W/ EXPLICIT "ORDER_BY" -;; `breakout` should not implicitly order by any fields specified in `order-by` -(qp.test/expect-with-non-timeseries-dbs - {:rows [[15 2 1] [15 3 1] [15 7 1] [15 14 1] [15 16 1] [15 18 1] [15 22 1] [15 23 2] [15 24 1] [15 27 1]] - :cols [(qp.test/breakout-col :checkins :user_id) - (qp.test/breakout-col :checkins :venue_id) - (qp.test/aggregate-col :count)]} - (qp.test/rows-and-cols - (qp.test/format-rows-by [int int int] - (data/run-mbql-query checkins - {:aggregation [[:count]] - :breakout [$user_id $venue_id] - :order-by [[:desc $user_id]] - :limit 10})))) + (testing "with explicit order by" + (testing "`breakout` should not implicitly order by any fields specified in `order-by`" + (is (= {:rows [[15 2 1] [15 3 1] [15 7 1] [15 14 1] [15 16 1] [15 18 1] [15 22 1] [15 23 2] [15 24 1] [15 27 1]] + :cols [(qp.test/breakout-col :checkins :user_id) + (qp.test/breakout-col :checkins :venue_id) + (qp.test/aggregate-col :count)]} + (qp.test/rows-and-cols + (mt/format-rows-by [int int int] + (mt/run-mbql-query checkins + {:aggregation [[:count]] + :breakout [$user_id $venue_id] + :order-by [[:desc $user_id]] + :limit 10})))))))))) (deftest internal-remapping-test (mt/test-drivers (mt/normal-drivers) - (data/with-temp-objects + (mt/with-temp-objects (data/create-venue-category-remapping "Foo") (let [{:keys [rows cols]} (qp.test/rows-and-cols - (qp.test/format-rows-by [int int str] - (data/run-mbql-query venues + (mt/format-rows-by [int int str] + (mt/run-mbql-query venues {:aggregation [[:count]] :breakout [$category_id] :limit 5})))] (is (= [(assoc (qp.test/breakout-col :venues :category_id) :remapped_to "Foo") (qp.test/aggregate-col :count) - (#'add-dim-projections/create-remapped-col "Foo" (data/format-name "category_id") :type/Text)] + (#'add-dim-projections/create-remapped-col "Foo" (mt/format-name "category_id") :type/Text)] cols)) (is (= [[2 8 "American"] [3 2 "Artisan"] @@ -98,102 +92,87 @@ rows)))))) (deftest order-by-test - (datasets/test-drivers (mt/normal-drivers-with-feature :foreign-keys) - (data/with-temp-objects + (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys) + (mt/with-temp-objects (fn [] [(db/insert! Dimension {:field_id (data/id :venues :category_id) :name "Foo" :type :external :human_readable_field_id (data/id :categories :name)})]) - (are [expected sort-order] (testing (format "sort order = %s" sort-order) - (is (= expected - (->> (data/run-mbql-query venues - {:order-by [[sort-order $category_id]] - :limit 10}) - qp.test/rows - (mapv last))))) - ["Wine Bar" "Thai" "Thai" "Thai" "Thai" "Steakhouse" "Steakhouse" "Steakhouse" "Steakhouse" "Southern"] - :desc - - ["American" "American" "American" "American" "American" "American" "American" "American" "Artisan" "Artisan"] - :asc)))) - -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :num-bins 20]]}))) + (doseq [[sort-order expected] {:desc ["Wine Bar" "Thai" "Thai" "Thai" "Thai" "Steakhouse" "Steakhouse" + "Steakhouse" "Steakhouse" "Southern"] + :asc ["American" "American" "American" "American" "American" "American" "American" + "American" "Artisan" "Artisan"]}] + (testing (format "sort order = %s" sort-order) + (is (= expected + (->> (mt/run-mbql-query venues + {:order-by [[sort-order $category_id]] + :limit 10}) + qp.test/rows + (mapv last))))))))) -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[0.0 1] [20.0 90] [40.0 9]] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :num-bins 3]]}))) +(deftest binning-test + (mt/test-drivers (mt/normal-drivers-with-feature :binning) + (testing "Bin single column" + (testing "20 bins" + (is (= [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :num-bins 20]]}))))) -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 -170.0 1] [32.0 -120.0 4] [34.0 -120.0 57] [36.0 -125.0 29] [40.0 -75.0 9]] - (qp.test/formatted-rows [1.0 1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :num-bins 20] - [:binning-strategy $longitude :num-bins 20]]}))) + (testing "3 bins" + (is (= [[0.0 1] [20.0 90] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :num-bins 3]]})))))) -;; Currently defaults to 8 bins when the number of bins isn't -;; specified -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [30.0 90] [40.0 9]] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :default]]}))) + (testing "Bin two columns" + (is (= [[10.0 -170.0 1] [32.0 -120.0 4] [34.0 -120.0 57] [36.0 -125.0 29] [40.0 -75.0 9]] + (mt/formatted-rows [1.0 1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :num-bins 20] + [:binning-strategy $longitude :num-bins 20]]}))))) -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [30.0 61] [35.0 29] [40.0 9]] - (tu/with-temporary-setting-values [breakout-bin-width 5.0] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :default]]})))) + (testing "should default to 8 bins when number of bins isn't specified" + (is (= [[10.0 1] [30.0 90] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :default]]})))) -;; Can I use `:default` binning in a nested query? -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [30.0 61] [35.0 29] [40.0 9]] - (tu/with-temporary-setting-values [breakout-bin-width 5.0] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:source-query - {:source-table $$venues - :aggregation [[:count]] - :breakout [[:binning-strategy $latitude :default]]}})))) + (mt/with-temporary-setting-values [breakout-bin-width 5.0] + (is (= [[10.0 1] [30.0 61] [35.0 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :default]]})))))) -;; Testing bin-width -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [33.0 4] [34.0 57] [37.0 29] [40.0 9]] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :bin-width 1]]}))) + (testing "bin width" + (is (= [[10.0 1] [33.0 4] [34.0 57] [37.0 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :bin-width 1]]})))) -;; Testing bin-width using a float -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[10.0 1] [32.5 61] [37.5 29] [40.0 9]] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :breakout [[:binning-strategy $latitude :bin-width 2.5]]}))) + (testing "using a float" + (is (= [[10.0 1] [32.5 61] [37.5 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :breakout [[:binning-strategy $latitude :bin-width 2.5]]})))) -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning) - [[33.0 4] [34.0 57]] - (tu/with-temporary-setting-values [breakout-bin-width 1.0] - (qp.test/formatted-rows [1.0 int] - (data/run-mbql-query venues - {:aggregation [[:count]] - :filter [:and - [:< $latitude 35] - [:> $latitude 20]] - :breakout [[:binning-strategy $latitude :default]]})))) + (mt/with-temporary-setting-values [breakout-bin-width 1.0] + (is (= [[33.0 4] [34.0 57]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:aggregation [[:count]] + :filter [:and + [:< $latitude 35] + [:> $latitude 20]] + :breakout [[:binning-strategy $latitude :default]]}))))))))) (defn- round-binning-decimals [result] (let [round-to-decimal #(u/round-to-decimals 4 %)] @@ -212,7 +191,7 @@ :binning_info {:min_value 10.0, :max_value 50.0, :num_bins 4, :bin_width 10.0, :binning_strategy :bin-width} :field_ref [:binning-strategy (data/$ids venues $latitude) :bin-width nil {:min-value 10.0, :max-value 50.0, :num-bins 4, :bin-width 10.0}]) - (-> (data/run-mbql-query venues + (-> (mt/run-mbql-query venues {:aggregation [[:count]] :breakout [[:binning-strategy $latitude :default]]}) qp.test/cols @@ -224,7 +203,7 @@ :binning_info {:min_value 7.5, :max_value 45.0, :num_bins 5, :bin_width 7.5, :binning_strategy :num-bins} :field_ref [:binning-strategy (data/$ids venues $latitude) :num-bins 5 {:min-value 7.5, :max-value 45.0, :num-bins 5, :bin-width 7.5}]) - (-> (data/run-mbql-query venues + (-> (mt/run-mbql-query venues {:aggregation [[:count]] :breakout [[:binning-strategy $latitude :num-bins 5]]}) qp.test/cols @@ -233,13 +212,13 @@ (deftest binning-error-test (mt/test-drivers (mt/normal-drivers-with-feature :binning) - (tu.log/suppress-output - (tu/with-temp-vals-in-db Field (data/id :venues :latitude) {:fingerprint {:type {:type/Number {:min nil, :max nil}}}} + (mt/suppress-output + (mt/with-temp-vals-in-db Field (data/id :venues :latitude) {:fingerprint {:type {:type/Number {:min nil, :max nil}}}} (is (= {:status :failed :class clojure.lang.ExceptionInfo :error "Unable to bin Field without a min/max value"} (-> (qp/process-userland-query - (data/mbql-query venues + (mt/mbql-query venues {:aggregation [[:count]] :breakout [[:binning-strategy $latitude :default]]})) (select-keys [:status :class :error])))))))) @@ -249,36 +228,48 @@ :type :query :query {:source-table (str "card__" (u/get-id card-or-card-id)) :aggregation [:count] - :breakout [[:binning-strategy [:field-literal (data/format-name :latitude) :type/Float] :num-bins 20]]}}) + :breakout [[:binning-strategy [:field-literal (mt/format-name :latitude) :type/Float] :num-bins 20]]}}) -;; Binning should be allowed on nested queries that have result metadata -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning :nested-queries) - [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]] - (tt/with-temp Card [card (qp.test-util/card-with-source-metadata-for-query - (data/mbql-query nil - {:source-query {:source-table $$venues}}))] - (qp.test/formatted-rows [1.0 int] - (qp/process-query - (nested-venues-query card))))) +(deftest bin-nested-queries-test + (mt/test-drivers (mt/normal-drivers-with-feature :binning :nested-queries) + (testing "Binning should be allowed on nested queries that have result metadata" + (mt/with-temp Card [card (qp.test-util/card-with-source-metadata-for-query + (mt/mbql-query nil + {:source-query {:source-table $$venues}}))] + (is (= [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (qp/process-query + (nested-venues-query card))))))) -;; Binning is not supported when there is no fingerprint to determine boundaries -(datasets/expect-with-drivers (mt/normal-drivers-with-feature :binning :nested-queries) - Exception - (tu.log/suppress-output - ;; Unfortunately our new `add-source-metadata` middleware is just too good at what it does and will pull in - ;; metadata from the source query, so disable that for now so we can make sure the `update-binning-strategy` - ;; middleware is doing the right thing - (with-redefs [add-source-metadata/mbql-source-query->metadata (constantly nil)] - (tt/with-temp Card [card {:dataset_query (data/mbql-query venues)}] - (qp.test/rows - (qp/process-query - (nested-venues-query card))))))) + (testing "should be able to use :default binning in a nested query" + (mt/with-temporary-setting-values [breakout-bin-width 5.0] + (is (= [[10.0 1] [30.0 61] [35.0 29] [40.0 9]] + (mt/formatted-rows [1.0 int] + (mt/run-mbql-query venues + {:source-query + {:source-table $$venues + :aggregation [[:count]] + :breakout [[:binning-strategy $latitude :default]]}})))))) -;; if we include a Field in both breakout and fields, does the query still work? (Normalization should be taking care -;; of this) (#8760) -(qp.test/expect-with-non-timeseries-dbs - :completed - (:status - (data/run-mbql-query venues - {:breakout [$price] - :fields [$price]}))) + (testing "Binning is not supported when there is no fingerprint to determine boundaries" + ;; Unfortunately our new `add-source-metadata` middleware is just too good at what it does and will pull in + ;; metadata from the source query, so disable that for now so we can make sure the `update-binning-strategy` + ;; middleware is doing the right thing + (with-redefs [add-source-metadata/mbql-source-query->metadata (constantly nil)] + (mt/with-temp Card [card {:dataset_query (mt/mbql-query venues)}] + (is (thrown? + Exception + (mt/suppress-output + (qp.test/rows + (qp/process-query + (nested-venues-query card))))))))))) + +(deftest field-in-breakout-and-fields-test + (mt/test-drivers (mt/normal-drivers) + (testing (str "if we include a Field in both breakout and fields, does the query still work? (Normalization should " + "be taking care of this) (#8760)") + (is (= :completed + (:status + (mt/run-mbql-query venues + {:breakout [$price] + :fields [$price]}))))))) diff --git a/test/metabase/query_processor_test/date_bucketing_test.clj b/test/metabase/query_processor_test/date_bucketing_test.clj index 00daa8b625cdaf01759eb9d770b384d184dc33f3..13aebbb637592a36c434aea517f53db6878bd7f3 100644 --- a/test/metabase/query_processor_test/date_bucketing_test.clj +++ b/test/metabase/query_processor_test/date_bucketing_test.clj @@ -861,7 +861,9 @@ ;; Create timestamps using relative dates (e.g. `DATEADD(second, -195, GETUTCDATE())` instead of ;; generating Java classes here so they'll be in the DB's native timezone. Some DBs refuse to use ;; the same timezone we're running the tests from *cough* SQL Server *cough* - [(u/prog1 (if (isa? driver/hierarchy driver/*driver* :sql) + [(u/prog1 (if (and (isa? driver/hierarchy driver/*driver* :sql) + ;; BigQuery doesn't insert rows using SQL statements + (not= driver/*driver* :bigquery)) (sql.qp/add-interval-honeysql-form driver/*driver* (sql.qp/current-datetime-honeysql-form driver/*driver*) (* i interval-seconds) @@ -901,20 +903,21 @@ (cons :relative-datetime relative-datetime-args)]}) mt/first-row first int))))) -;; HACK - Don't run these tests against BigQuery/etc. because the databases need to be loaded every time the tests are ran -;; and loading data into BigQuery/etc. is mind-bogglingly slow. Don't worry, I promise these work though! +;; HACK - Don't run these tests against Snowflake/etc. because the databases need to be loaded every time the tests +;; are ran and loading data into these DBs is mind-bogglingly slow. +;; +;; Don't run the minute tests against Oracle because the Oracle tests are kind of slow and case CI to fail randomly +;; when it takes so long to load the data that the times are no longer current (these tests pass locally if your +;; machine isn't as slow as the CircleCI ones) (deftest count-of-grouping-test - ;; Don't run the minute tests against Oracle because the Oracle tests are kind of slow and case CI to fail randomly - ;; when it takes so long to load the data that the times are no longer current (these tests pass locally if your - ;; machine isn't as slow as the CircleCI ones) - (mt/test-drivers (mt/normal-drivers-except #{:snowflake :bigquery :oracle}) + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) (testing "4 checkins per minute dataset" (testing "group by minute" (doseq [args [[:current] [-1 :minute] [1 :minute]]] (is (= 4 (apply count-of-grouping checkins:4-per-minute :minute args)) (format "filter by minute = %s" (into [:relative-datetime] args))))))) - (mt/test-drivers (mt/normal-drivers-except #{:snowflake :bigquery}) + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) (testing "4 checkins per hour dataset" (testing "group by hour" (doseq [args [[:current] [-1 :hour] [1 :hour]]] @@ -933,21 +936,20 @@ "filter by week = [:relative-datetime :current]"))))) (deftest time-interval-test - (mt/test-drivers (mt/normal-drivers-except #{:snowflake :bigquery}) + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) (testing "Syntactic sugar (`:time-interval` clause)" - (is (= 1 - (-> (mt/dataset checkins:1-per-day - (mt/run-mbql-query checkins + (mt/dataset checkins:1-per-day + (is (= 1 + (-> (mt/run-mbql-query checkins {:aggregation [[:count]] - :filter [:time-interval $timestamp :current :day]})) - mt/first-row first int))) + :filter [:time-interval $timestamp :current :day]}) + mt/first-row first int))) - (is (= 7 - (-> (mt/dataset checkins:1-per-day - (mt/run-mbql-query checkins + (is (= 7 + (-> (mt/run-mbql-query checkins {:aggregation [[:count]] - :filter [:time-interval $timestamp :last :week]})) - mt/first-row first int)))))) + :filter [:time-interval $timestamp :last :week]}) + mt/first-row first int))))))) ;; Make sure that when referencing the same field multiple times with different units we return the one that actually ;; reflects the units the results are in. eg when we breakout by one unit and filter by another, make sure the results @@ -964,7 +966,7 @@ :unit (-> results :data :cols first :unit)})) (deftest date-bucketing-when-you-test - (mt/test-drivers (mt/normal-drivers-except #{:snowflake :bigquery}) + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) (is (= {:rows 1, :unit :day} (date-bucketing-unit-when-you :breakout-by "day", :filter-by "day"))) (is (= {:rows 7, :unit :day} @@ -992,15 +994,16 @@ ;; We should get count = 1 for the current day, as opposed to count = 0 if we weren't auto-bucketing ;; (e.g. 2018-11-19T00:00 != 2018-11-19T12:37 or whatever time the checkin is at) (deftest default-bucketing-test - (mt/test-drivers (mt/normal-drivers-except #{:snowflake :bigquery}) - (is (= [[1]] - (mt/formatted-rows [int] - (mt/dataset checkins:1-per-day + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) + (mt/dataset checkins:1-per-day + (is (= [[1]] + (mt/formatted-rows [int] (mt/run-mbql-query checkins {:aggregation [[:count]] :filter [:= [:field-id $timestamp] (t/format "yyyy-MM-dd" (u.date/truncate :day))]})))))) + ;; this is basically the same test as above, but using the office-checkins dataset instead of the dynamically - ;; created checkins DBs so we can run it against Snowflake and BigQuery as well. + ;; created checkins DBs so we can run it against Snowflake as well. (mt/test-drivers (mt/normal-drivers) (mt/dataset office-checkins (is (= [[1]] @@ -1016,11 +1019,12 @@ {:aggregation [[:count]] :filter [:and [:= [:field-id $timestamp] "2019-01-16"] - [:= [:field-id $id] 6]]})))))) + [:= [:field-id $id] 6]]}))))))) + (mt/test-drivers (mt/normal-drivers-except #{:snowflake}) (testing "if datetime string is not yyyy-MM-dd no date bucketing should take place, and thus we should get no (exact) matches" (mt/dataset checkins:1-per-day - (is (= ;; Mongo returns empty row for count = 0. We should fix that + (is (= ;; Mongo returns empty row for count = 0. We should fix that (#5419) (case driver/*driver* :mongo [] [[0]]) @@ -1064,13 +1068,13 @@ (name unit) filter-value (str/join ", " (sort expected-count))))))))))) (deftest legacy-default-datetime-bucketing-test - (is (= (str "SELECT count(*) AS \"count\" " - "FROM \"PUBLIC\".\"CHECKINS\" " - "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = CAST(now() AS date)") - (:query - (qp/query->native - (mt/mbql-query checkins - {:aggregation [[:count]] - :filter [:= $date [:relative-datetime :current]]})))) - (str "Datetime fields that aren't wrapped in datetime-field clauses should get default :day bucketing for legacy " - "reasons. See #9014"))) + (testing (str "Datetime fields that aren't wrapped in datetime-field clauses should get default :day bucketing for " + "legacy reasons. See #9014") + (is (= (str "SELECT count(*) AS \"count\" " + "FROM \"PUBLIC\".\"CHECKINS\" " + "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = CAST(now() AS date)") + (:query + (qp/query->native + (mt/mbql-query checkins + {:aggregation [[:count]] + :filter [:= $date [:relative-datetime :current]]}))))))) diff --git a/test/metabase/query_processor_test/implicit_joins_test.clj b/test/metabase/query_processor_test/implicit_joins_test.clj index 71b83711b8b67a92101a68c598b0f3f6225340ef..9117f70b092aa1191dc978fe0b10a955715bba84 100644 --- a/test/metabase/query_processor_test/implicit_joins_test.clj +++ b/test/metabase/query_processor_test/implicit_joins_test.clj @@ -72,28 +72,27 @@ ;; 2. Check that we can join MULTIPLE tables in a single query ;; (this query joins both cities and categories) (datasets/expect-with-drivers (mt/normal-drivers-with-feature :foreign-keys) - ;; CITY_ID, CATEGORY_ID, ID + ;; ID, CITY_ID, CATEGORY_ID ;; Cities are already alphabetized in the source data which is why CITY_ID is sorted - [[1 12 6] - [1 11 355] - [1 11 596] - [1 13 379] - [1 5 413] - [1 1 426] - [2 11 67] - [2 11 524] - [2 13 77] - [2 13 202]] + [[6 1 12] + [355 1 11] + [596 1 11] + [379 1 13] + [413 1 5] + [426 1 1] + [67 2 11] + [524 2 11] + [77 2 13] + [202 2 13]] (->> (mt/dataset tupac-sightings (mt/run-mbql-query sightings {:order-by [[:asc $city_id->cities.name] [:desc $category_id->categories.name] [:asc $id]] :limit 10})) - ;; drop timestamps. reverse ordering to make the results columns order match order-by + ;; drop timestamps. qp.test/rows (map butlast) - (map reverse) (qp.test/format-rows-by [int int int]))) diff --git a/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj b/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj index a6f8a5605db2f427c3b113fcbbb2eece47b70089..132ae8e5be9e1e8005e24e29cd89fee55150019f 100644 --- a/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj +++ b/test/metabase/sync/sync_metadata/fields/fetch_metadata_test.clj @@ -1,66 +1,79 @@ (ns metabase.sync.sync-metadata.fields.fetch-metadata-test - (:require [clojure.walk :as walk] - [expectations :refer [expect]] + (:require [clojure + [test :refer :all] + [walk :as walk]] [medley.core :as m] + [metabase + [test :as mt] + [util :as u]] [metabase.models [database :refer [Database]] [table :refer [Table]]] [metabase.sync.sync-metadata :as sync-metadata] [metabase.sync.sync-metadata.fields.fetch-metadata :as sync-fields.fetch-metadata] [metabase.test.mock.toucanery :as toucanery] - [metabase.util :as u] - [toucan.db :as db] - [toucan.util.test :as tt])) + [toucan.db :as db])) ;; `our-metadata` should match up with what we have in the DB -(expect - #{{:name "id" - :database-type "SERIAL" - :base-type :type/Integer - :special-type :type/PK - :pk? true} - {:name "buyer" - :database-type "OBJECT" - :base-type :type/Dictionary - :pk? false - :nested-fields #{{:name "name" - :database-type "VARCHAR" - :base-type :type/Text - :pk? false} - {:name "cc" - :database-type "VARCHAR" - :base-type :type/Text - :pk? false}}} - {:name "ts" - :database-type "BIGINT" - :base-type :type/BigInteger - :special-type :type/UNIXTimestampMilliseconds - :pk? false} - {:name "toucan" - :database-type "OBJECT" - :base-type :type/Dictionary - :pk? false - :nested-fields #{{:name "name" - :database-type "VARCHAR" - :base-type :type/Text - :pk? false} - {:name "details" - :database-type "OBJECT" - :base-type :type/Dictionary - :pk? false - :nested-fields #{{:name "weight" - :database-type "DECIMAL" - :base-type :type/Decimal - :special-type :type/Category - :pk? false} - {:name "age" - :database-type "INT" - :base-type :type/Integer - :pk? false}}}}}} - (tt/with-temp* [Database [db {:engine ::toucanery/toucanery}]] +(deftest does-metadata-match-test + (mt/with-temp Database [db {:engine ::toucanery/toucanery}] (sync-metadata/sync-db-metadata! db) - (let [transactions-table-id (u/get-id (db/select-one-id Table :db_id (u/get-id db), :name "transactions")) - remove-ids-and-nil-vals (partial walk/postwalk #(if-not (map? %) - % - (m/filter-vals some? (dissoc % :id))))] - (remove-ids-and-nil-vals (#'sync-fields.fetch-metadata/our-metadata (Table transactions-table-id)))))) + (is (= #{{:name "id" + :database-type "SERIAL" + :base-type :type/Integer + :special-type :type/PK + :pk? true + :database-position 1} + {:name "buyer" + :database-type "OBJECT" + :base-type :type/Dictionary + :pk? false + :database-position 2 + :nested-fields #{{:name "name" + :database-type "VARCHAR" + :base-type :type/Text + :pk? false + :database-position 2} + {:name "cc" + :database-type "VARCHAR" + :base-type :type/Text + :pk? false + :database-position 2}}} + {:name "ts" + :database-type "BIGINT" + :base-type :type/BigInteger + :special-type :type/UNIXTimestampMilliseconds + :pk? false + :database-position 0} + {:name "toucan" + :database-type "OBJECT" + :base-type :type/Dictionary + :pk? false + :database-position 3 + :nested-fields #{{:name "name" + :database-type "VARCHAR" + :base-type :type/Text + :pk? false + :database-position 3} + {:name "details" + :database-type "OBJECT" + :base-type :type/Dictionary + :pk? false + :database-position 3 + :nested-fields #{{:name "weight" + :database-type "DECIMAL" + :base-type :type/Decimal + :special-type :type/Category + :pk? false + :database-position 3} + {:name "age" + :database-type "INT" + :base-type :type/Integer + :pk? false + :database-position 3}}}}}} + + (let [transactions-table-id (u/get-id (db/select-one-id Table :db_id (u/get-id db), :name "transactions")) + remove-ids-and-nil-vals (partial walk/postwalk #(if-not (map? %) + % + (m/filter-vals some? (dissoc % :id))))] + (remove-ids-and-nil-vals (#'sync-fields.fetch-metadata/our-metadata (Table transactions-table-id)))))))) diff --git a/test/metabase/sync/sync_metadata/fields/sync_instances_test.clj b/test/metabase/sync/sync_metadata/fields/sync_instances_test.clj index f476941e2457f5eeac21cc293cd98a332f5c35fa..5393ef5ef24212018126bc5f0f9a58160994105b 100644 --- a/test/metabase/sync/sync_metadata/fields/sync_instances_test.clj +++ b/test/metabase/sync/sync_metadata/fields/sync_instances_test.clj @@ -1,5 +1,5 @@ (ns metabase.sync.sync-metadata.fields.sync-instances-test - (:require [expectations :refer [expect]] + (:require [clojure.test :refer :all] [metabase.models [database :refer [Database]] [field :refer [Field]] @@ -27,30 +27,30 @@ (format-fields nested-fields))])))] (format-fields (get parent-id->children nil)))) -(expect - toucannery-transactions-expected-fields-hierarchy +(deftest sync-fields-test (tt/with-temp* [Database [db {:engine ::toucanery/toucanery}] Table [table {:name "transactions", :db_id (u/get-id db)}]] ;; do the initial sync (sync-fields/sync-fields-for-table! table) (let [transactions-table-id (u/get-id (db/select-one-id Table :db_id (u/get-id db), :name "transactions"))] - (actual-fields-hierarchy transactions-table-id)))) + (is (= toucannery-transactions-expected-fields-hierarchy + (actual-fields-hierarchy transactions-table-id)))))) -;; If you delete a nested Field, and re-sync a Table, it should recreate the Field as it was before! It should not -;; create any duplicate Fields (#8950) -(expect - toucannery-transactions-expected-fields-hierarchy - (tt/with-temp* [Database [db {:engine ::toucanery/toucanery}] - Table [table {:name "transactions", :db_id (u/get-id db)}]] - ;; do the initial sync - (sync-fields/sync-fields-for-table! table) - (let [transactions-table-id (u/get-id (db/select-one-id Table :db_id (u/get-id db), :name "transactions"))] - ;; Give the Table a new Hash, and delete `toucan.details.age` - (db/update! Table transactions-table-id :fields_hash "something new") - (db/delete! Field :table_id transactions-table-id, :name "age") - ;; ok, resync the Table. `toucan.details.age` should be recreated, but only one. We should *not* have a - ;; `toucan.age` Field as well, which was happening before the bugfix in this PR +(deftest delete-nested-field-test + (testing (str "If you delete a nested Field, and re-sync a Table, it should recreate the Field as it was before! It " + "should not create any duplicate Fields (#8950)") + (tt/with-temp* [Database [db {:engine ::toucanery/toucanery}] + Table [table {:name "transactions", :db_id (u/get-id db)}]] + ;; do the initial sync (sync-fields/sync-fields-for-table! table) - ;; Fetch all the Fields in the `transactions` Table (name & parent name) after the sync, format them in a - ;; hierarchy for easy comparison - (actual-fields-hierarchy transactions-table-id)))) + (let [transactions-table-id (u/get-id (db/select-one-id Table :db_id (u/get-id db), :name "transactions"))] + ;; Give the Table a new Hash, and delete `toucan.details.age` + (db/update! Table transactions-table-id :fields_hash "something new") + (db/delete! Field :table_id transactions-table-id, :name "age") + ;; ok, resync the Table. `toucan.details.age` should be recreated, but only one. We should *not* have a + ;; `toucan.age` Field as well, which was happening before the bugfix in this PR + (sync-fields/sync-fields-for-table! table) + ;; Fetch all the Fields in the `transactions` Table (name & parent name) after the sync, format them in a + ;; hierarchy for easy comparison + (is (= toucannery-transactions-expected-fields-hierarchy + (actual-fields-hierarchy transactions-table-id))))))) diff --git a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj index 01ff41e72329bd18804a9611ae8d43e02689632c..c9c6d70c0fad7dc2be85cbf1598d567659fb0257 100644 --- a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj +++ b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj @@ -1,5 +1,5 @@ (ns metabase.sync.sync-metadata.fields.sync-metadata-test - (:require [expectations :refer [expect]] + (:require [clojure.test :refer :all] [metabase.models.table :refer [Table]] [metabase.sync.sync-metadata.fields.sync-metadata :as sync-metadata] [toucan.db :as db] @@ -16,51 +16,60 @@ metadata-in-application-db) @update-operations)))) -;; test that if database-type changes we will update it in the DB -(expect - [["Field" 1 {:database_type "Integer"}]] - (updates-that-will-be-performed - {:name "My Field" - :database-type "Integer" - :base-type :type/Integer} - {:name "My Field" - :database-type "NULL" - :base-type :type/Integer - :id 1})) +(deftest database-type-changed-test + (testing "test that if database-type changes we will update it in the DB" + (is (= [["Field" 1 {:database_type "Integer"}]] + (updates-that-will-be-performed + {:name "My Field" + :database-type "Integer" + :base-type :type/Integer + :database-position 0} + {:name "My Field" + :database-type "NULL" + :base-type :type/Integer + :id 1 + :database-position 0}))))) -;; no changes should be made (i.e., no calls to `update!`) if nothing changes -(expect - [] - (updates-that-will-be-performed - {:name "My Field" - :database-type "Integer" - :base-type :type/Integer} - {:name "My Field" - :database-type "Integer" - :base-type :type/Integer - :id 1})) +(deftest no-op-test + (testing "no changes should be made (i.e., no calls to `update!`) if nothing changes" + (is (= [] + (updates-that-will-be-performed + {:name "My Field" + :database-type "Integer" + :base-type :type/Integer + :database-position 0} + {:name "My Field" + :database-type "Integer" + :base-type :type/Integer + :id 1 + :database-position 0}))))) -;; test that if `database-type` comes back as `nil` in the metadata from the sync process, we won't try to set a `nil` -;; value in the DB -- this is against the rules -- we should set `NULL` instead. See `TableMetadataField` schema. -(expect - [["Field" 1 {:database_type "NULL"}]] - (updates-that-will-be-performed - {:name "My Field" - :database-type nil - :base-type :type/Integer} - {:name "My Field" - :database-type "Integer" - :base-type :type/Integer - :id 1})) +(deftest nil-database-type-test + (testing (str "test that if `database-type` comes back as `nil` in the metadata from the sync process, we won't try " + "to set a `nil` value in the DB -- this is against the rules -- we should set `NULL` instead. See " + "`TableMetadataField` schema.") + (is (= [["Field" 1 {:database_type "NULL"}]] + (updates-that-will-be-performed + {:name "My Field" + :database-type nil + :base-type :type/Integer + :database-position 0} + {:name "My Field" + :database-type "Integer" + :base-type :type/Integer + :database-position 0 + :id 1})))) -;; if `database-type` comes back as `nil` and was already saved in application DB as `NULL` no changes should be made -(expect - [] - (updates-that-will-be-performed - {:name "My Field" - :database-type nil - :base-type :type/Integer} - {:name "My Field" - :database-type "NULL" - :base-type :type/Integer - :id 1})) + (testing (str "if `database-type` comes back as `nil` and was already saved in application DB as `NULL` no changes " + "should be made") + (is (= [] + (updates-that-will-be-performed + {:name "My Field" + :database-type nil + :base-type :type/Integer + :database-position 0} + {:name "My Field" + :database-type "NULL" + :base-type :type/Integer + :id 1 + :database-position 0}))))) diff --git a/test/metabase/sync/sync_metadata/tables_test.clj b/test/metabase/sync/sync_metadata/tables_test.clj index 899a4a9ceafe71c492d6bc69fd62798ec41ec7b7..29b4b5a98ac8ce82687ea533a07325749135bed4 100644 --- a/test/metabase/sync/sync_metadata/tables_test.clj +++ b/test/metabase/sync/sync_metadata/tables_test.clj @@ -1,15 +1,13 @@ (ns metabase.sync.sync-metadata.tables-test "Test for the logic that syncs Table models with the metadata fetched from a DB." - (:require [expectations :refer [expect]] - [metabase.models - [database :refer [Database]] - [table :refer [Table]]] + (:require [clojure.test :refer :all] + [metabase + [models :refer [Database Table]] + [test :as mt] + [util :as u]] [metabase.sync.sync-metadata.tables :as sync-tables] - [metabase.test.data :as data] [metabase.test.data.interface :as tx] - [metabase.util :as u] - [toucan.db :as db] - [toucan.util.test :as tt])) + [toucan.db :as db])) (tx/defdataset ^:private db-with-some-cruft [["acquired_toucans" @@ -25,19 +23,19 @@ [["main" "0001_initial"] ["main" "0002_add_toucans"]]]]) -;; south_migrationhistory, being a CRUFTY table, should still be synced, but marked as such -(expect - #{{:name "SOUTH_MIGRATIONHISTORY", :visibility_type :cruft} - {:name "ACQUIRED_TOUCANS", :visibility_type nil}} - (data/dataset metabase.sync.sync-metadata.tables-test/db-with-some-cruft - (set (for [table (db/select [Table :name :visibility_type], :db_id (data/id))] - (into {} table))))) +(deftest crufty-tables-test + (testing "south_migrationhistory, being a CRUFTY table, should still be synced, but marked as such" + (mt/dataset metabase.sync.sync-metadata.tables-test/db-with-some-cruft + (is (= #{{:name "SOUTH_MIGRATIONHISTORY", :visibility_type :cruft} + {:name "ACQUIRED_TOUCANS", :visibility_type nil}} + (set (for [table (db/select [Table :name :visibility_type], :db_id (mt/id))] + (into {} table)))))))) -;; `retire-tables!` should retire the Table(s) passed to it, not all Tables in the DB -- see #9593 -(expect - {"Table 1" false, "Table 2" true} - (tt/with-temp* [Database [db] - Table [table-1 {:name "Table 1", :db_id (u/get-id db)}] - Table [table-2 {:name "Table 2", :db_id (u/get-id db)}]] - (#'sync-tables/retire-tables! db #{{:name "Table 1", :schema (:schema table-1)}}) - (db/select-field->field :name :active Table, :db_id (u/get-id db)))) +(deftest retire-tables-test + (testing "`retire-tables!` should retire the Table(s) passed to it, not all Tables in the DB -- see #9593" + (mt/with-temp* [Database [db] + Table [table-1 {:name "Table 1", :db_id (u/get-id db)}] + Table [table-2 {:name "Table 2", :db_id (u/get-id db)}]] + (#'sync-tables/retire-tables! db #{{:name "Table 1", :schema (:schema table-1)}}) + (is (= {"Table 1" false, "Table 2" true} + (db/select-field->field :name :active Table, :db_id (u/get-id db))))))) diff --git a/test/metabase/sync_database_test.clj b/test/metabase/sync_database_test.clj index 00175628b19dc03236f9be6c9449694248a65aca..5e921c3ed9e7e6a0c1e8d385ce41782eaeb24781 100644 --- a/test/metabase/sync_database_test.clj +++ b/test/metabase/sync_database_test.clj @@ -28,26 +28,31 @@ (def ^:private sync-test-tables {"movie" {:name "movie" :schema "default" - :fields #{{:name "id" - :database-type "SERIAL" - :base-type :type/Integer - :special-type :type/PK} - {:name "title" - :database-type "VARCHAR" - :base-type :type/Text - :special-type :type/Title} - {:name "studio" - :database-type "VARCHAR" - :base-type :type/Text}}} + :fields #{{:name "id" + :database-type "SERIAL" + :base-type :type/Integer + :special-type :type/PK + :database-position 0} + {:name "title" + :database-type "VARCHAR" + :base-type :type/Text + :special-type :type/Title + :database-position 1} + {:name "studio" + :database-type "VARCHAR" + :base-type :type/Text + :database-position 2}}} "studio" {:name "studio" :schema nil - :fields #{{:name "studio" - :database-type "VARCHAR" - :base-type :type/Text - :special-type :type/PK} - {:name "name" - :database-type "VARCHAR" - :base-type :type/Text}}}}) + :fields #{{:name "studio" + :database-type "VARCHAR" + :base-type :type/Text + :special-type :type/PK + :database-position 0} + {:name "name" + :database-type "VARCHAR" + :base-type :type/Text + :database-position 1}}}}) (driver/register! ::sync-test, :abstract? true) @@ -103,7 +108,8 @@ :show_in_getting_started false :updated_at true :visibility_type nil - :fields_hash true}) + :fields_hash true + :field_order :database}) (def ^:private field-defaults {:active true @@ -119,6 +125,8 @@ :parent_id false :points_of_interest nil :position 0 + :database_position 0 + :custom_position 0 :preview_display true :special_type nil :table_id true @@ -135,11 +143,13 @@ (def ^:private field:movie-id (merge field-defaults - {:name "id" - :display_name "ID" - :database_type "SERIAL" - :base_type :type/Integer - :special_type :type/PK})) + {:name "id" + :display_name "ID" + :database_type "SERIAL" + :base_type :type/Integer + :special_type :type/PK + :database_position 0 + :position 0})) (def ^:private field:movie-studio (merge @@ -149,35 +159,43 @@ :database_type "VARCHAR" :base_type :type/Text :fk_target_field_id true - :special_type :type/FK})) + :special_type :type/FK + :database_position 2 + :position 2})) (def ^:private field:movie-title (merge field-defaults-with-fingerprint - {:name "title" - :display_name "Title" - :database_type "VARCHAR" - :base_type :type/Text - :special_type :type/Title})) + {:name "title" + :display_name "Title" + :database_type "VARCHAR" + :base_type :type/Text + :special_type :type/Title + :database_position 1 + :position 1})) (def ^:private field:studio-name (merge field-defaults-with-fingerprint - {:name "name" - :display_name "Name" - :database_type "VARCHAR" - :base_type :type/Text - :special_type :type/Name})) + {:name "name" + :display_name "Name" + :database_type "VARCHAR" + :base_type :type/Text + :special_type :type/Name + :database_position 1 + :position 1})) ;; `studio.studio`? huh? (def ^:private field:studio-studio (merge field-defaults - {:name "studio" - :display_name "Studio" - :database_type "VARCHAR" - :base_type :type/Text - :special_type :type/PK})) + {:name "studio" + :display_name "Studio" + :database_type "VARCHAR" + :base_type :type/Text + :special_type :type/PK + :database_position 0 + :position 0})) (deftest sync-database-test (mt/with-temp Database [db {:engine :metabase.sync-database-test/sync-test}] diff --git a/test/metabase/test.clj b/test/metabase/test.clj index ec360a2f2e789230b8faae8fdf5e4be1ffc3dc98..8e597be57798a30e3d3bc8e43109fb9f5ffa366b 100644 --- a/test/metabase/test.clj +++ b/test/metabase/test.clj @@ -4,6 +4,7 @@ (Prefer using `metabase.test` to requiring bits and pieces from these various namespaces going forward, since it reduces the cognitive load required to write tests.)" (:require [clojure + data [test :refer :all] [walk :as walk]] [java-time :as t] @@ -34,6 +35,7 @@ [log :as tu.log] [timezone :as tu.tz]] [potemkin :as p] + [toucan.db :as db] [toucan.util.test :as tt])) ;; Fool the linters into thinking these namespaces are used! See discussion on @@ -153,6 +155,7 @@ doall-recursive is-uuid-string? metabase-logger + obj->json->obj postwalk-pred random-email random-name @@ -277,3 +280,26 @@ (into {} form) form)) form)) + +(def ^{:arglists '([toucan-model])} object-defaults + "Return the default values for columns in an instance of a `toucan-model`, excluding ones that differ between + instances such as `:id`, `:name`, or `:created_at`. Useful for writing tests and comparing objects from the + application DB. Example usage: + + (deftest update-user-first-name-test + (mt/with-temp User [user] + (update-user-first-name! user \"Cam\") + (is (= (merge (mt/object-defaults User) + (select-keys user [:id :last_name :created_at :updated_at]) + {:name \"Cam\"}) + (mt/decrecordize (db/select-one User :id (:id user)))))))" + (comp + (memoize + (fn [toucan-model] + (with-temp* [toucan-model [x] + toucan-model [y]] + (let [[_ _ things-in-both] (clojure.data/diff x y)] + things-in-both)))) + (fn [toucan-model] + (initialize/initialize-if-needed! :db) + (db/resolve-model toucan-model)))) diff --git a/test/metabase/test/data/dataset_definitions.clj b/test/metabase/test/data/dataset_definitions.clj index f014ca8fe597f895afd18a294550f97b3a8481a6..7299f0abac8c5ef0a61e4fff729b93b43ee696e5 100644 --- a/test/metabase/test/data/dataset_definitions.clj +++ b/test/metabase/test/data/dataset_definitions.clj @@ -107,12 +107,15 @@ (update tabledef :field-definitions - concat - [(tx/map->FieldDefinition {:field-name "null_only_date", :base-type :type/Date})])) + (fn [[date-field-def user-id-field-def venue-id-field-def]] + [date-field-def + (tx/map->FieldDefinition {:field-name "null_only_date", :base-type :type/Date}) + user-id-field-def + venue-id-field-def]))) :rows (fn [rows] - (for [row rows] - (concat row [nil])))))) + (for [[date user-id venue-id] rows] + [date nil user-id venue-id]))))) (defonce ^{:doc "The main `test-data` dataset, but `last_login` has a base type of `:type/DateTimeWithTZ`."} test-data-with-timezones @@ -152,15 +155,15 @@ No Database we support supports all of these different types, so the expectation is that we'll use the closest equivalent for each column." [["attempts" - [{:field-name "num_crows", :base-type :type/Integer} - {:field-name "date", :base-type :type/Date} - {:field-name "time", :base-type :type/Time} - {:field-name "time_ltz", :base-type :type/TimeWithLocalTZ} - {:field-name "time_tz", :base-type :type/TimeWithZoneOffset} + [{:field-name "date", :base-type :type/Date} {:field-name "datetime", :base-type :type/DateTime} {:field-name "datetime_ltz", :base-type :type/DateTimeWithLocalTZ} {:field-name "datetime_tz", :base-type :type/DateTimeWithZoneOffset} - {:field-name "datetime_tz_id", :base-type :type/DateTimeWithZoneID}] + {:field-name "datetime_tz_id", :base-type :type/DateTimeWithZoneID} + {:field-name "time", :base-type :type/Time} + {:field-name "time_ltz", :base-type :type/TimeWithLocalTZ} + {:field-name "time_tz", :base-type :type/TimeWithZoneOffset} + {:field-name "num_crows", :base-type :type/Integer}] (for [[cnt t] [[6 #t "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"] [8 #t "2019-11-02T00:14:14.246-07:00[America/Los_Angeles]"] [6 #t "2019-11-03T23:35:17.906-08:00[America/Los_Angeles]"] @@ -181,12 +184,13 @@ [3 #t "2019-11-18T20:47:27.902-08:00[America/Los_Angeles]"] [5 #t "2019-11-19T00:35:23.146-08:00[America/Los_Angeles]"] [1 #t "2019-11-20T20:09:55.752-08:00[America/Los_Angeles]"]]] - [cnt ; num-crows - (t/local-date t) ; date - (t/local-time t) ; time - (t/offset-time t) ; time-ltz - (t/offset-time t) ; time-tz + [(t/local-date t) ; date (t/local-date-time t) ; datetime (t/offset-date-time t) ; datetime-ltz (t/offset-date-time t) ; datetime-tz - t])]]) ; datetime-tz-id + t ; datetime-tz-id + (t/local-time t) ; time + (t/offset-time t) ; time-ltz + (t/offset-time t) ; time-tz + cnt ; num-crows + ])]]) diff --git a/test/metabase/test/data/dataset_definitions/geographical-tips.edn b/test/metabase/test/data/dataset_definitions/geographical-tips.edn index 97750fc5a817cc924b5ec3a119bd96f98fc5d735..eadd6b1e291e6f7d9162f5c42197f2d335189f45 100644 --- a/test/metabase/test/data/dataset_definitions/geographical-tips.edn +++ b/test/metabase/test/data/dataset_definitions/geographical-tips.edn @@ -1,3008 +1,7003 @@ -[["tips" [{:field-name "text" +[["tips" [{:field-name "source" + :base-type :type/*} + {:field-name "text" :base-type :type/Text} {:field-name "url" :base-type :type/*} {:field-name "venue" - :base-type :type/*} - {:field-name "source" :base-type :type/*}] - [["Lucky's Gluten-Free Café is a atmospheric and delicious place to have a drink during winter." - {:small "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/small.jpg", - :medium "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/med.jpg", - :large "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/large.jpg"} - {:name "Lucky's Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-740-2328", :id "379af987-ad40-4a93-88a6-0233e1c14649"} - {:service "facebook", :facebook-photo-id "e46749c3-c532-4dc7-bdc3-da274de3c3ab", :url "http://facebook.com/photos/e46749c3-c532-4dc7-bdc3-da274de3c3ab"}] - ["Joe's Homestyle Eatery is a exclusive and historical place to have breakfast on public holidays." - {:small "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/small.jpg", - :medium "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/med.jpg", - :large "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "flare", :username "mandy"}] - ["Lower Pac Heights Cage-Free Coffee House is a underground and fantastic place to catch a bite to eat on Saturday night." - {:small "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/small.jpg", - :medium "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/med.jpg", - :large "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "flare", :username "mandy"}] - ["Oakland European Liquor Store is a swell and wonderful place to take a date in July." - {:small "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/small.jpg", - :medium "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/med.jpg", - :large "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/large.jpg"} - {:name "Oakland European Liquor Store", :categories ["European" "Liquor Store"], :phone "415-559-1516", :id "e342e7b7-e82d-475d-a822-b2df9c84850d"} - {:service "facebook", :facebook-photo-id "a9855e18-8612-4a97-b86a-aafe0d747bb4", :url "http://facebook.com/photos/a9855e18-8612-4a97-b86a-aafe0d747bb4"}] - ["Tenderloin Gormet Restaurant is a world-famous and popular place to have a drink during winter." - {:small "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/small.jpg", - :medium "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/med.jpg", - :large "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/large.jpg"} - {:name "Tenderloin Gormet Restaurant", :categories ["Gormet" "Restaurant"], :phone "415-127-4197", :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"} - {:service "twitter", :mentions ["@tenderloin_gormet_restaurant"], :tags ["#gormet" "#restaurant"], :username "tupac"}] - ["Marina Modern Sushi is a fantastic and underappreciated place to have a birthday party weekday afternoons." - {:small "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/small.jpg", - :medium "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/med.jpg", - :large "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/large.jpg"} - {:name "Marina Modern Sushi", :categories ["Modern" "Sushi"], :phone "415-393-7672", :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"} - {:service "facebook", :facebook-photo-id "58084e61-6381-4313-83be-6e35c8424600", :url "http://facebook.com/photos/58084e61-6381-4313-83be-6e35c8424600"}] - ["Sunset Homestyle Grill is a world-famous and well-decorated place to conduct a business meeting Friday nights." - {:small "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/small.jpg", - :medium "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/med.jpg", - :large "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "yelp", :yelp-photo-id "976fdffd-70ca-4b57-b214-80f0eb80f619", :categories ["Homestyle" "Grill"]}] - ["Kyle's Low-Carb Grill is a delicious and underappreciated place to have a after-work cocktail after baseball games." - {:small "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/small.jpg", - :medium "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/med.jpg", - :large "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "yelp", :yelp-photo-id "058b7d4e-b92b-4408-ba67-659f5b640e4b", :categories ["Low-Carb" "Grill"]}] - ["Mission Homestyle Churros is a groovy and wonderful place to catch a bite to eat weekday afternoons." - {:small "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/small.jpg", - :medium "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/med.jpg", - :large "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "facebook", :facebook-photo-id "b2a4862c-c5b7-4cfb-8ff6-fb564c28f4c8", :url "http://facebook.com/photos/b2a4862c-c5b7-4cfb-8ff6-fb564c28f4c8"}] - ["Sameer's Pizza Liquor Store is a overrated and decent place to sip a glass of expensive wine during summer." - {:small "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/small.jpg", - :medium "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/med.jpg", - :large "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "twitter", :mentions ["@sameers_pizza_liquor_store"], :tags ["#pizza" "#liquor" "#store"], :username "cam_saul"}] - ["Market St. European Ice Cream Truck is a overrated and overrated place to watch the Giants game on Saturday night." - {:small "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/small.jpg", - :medium "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/med.jpg", - :large "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/large.jpg"} - {:name "Market St. European Ice Cream Truck", :categories ["European" "Ice Cream Truck"], :phone "415-555-4197", :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"} - {:service "facebook", :facebook-photo-id "d3717643-c5c3-4e36-8721-99c2fd65972f", :url "http://facebook.com/photos/d3717643-c5c3-4e36-8721-99c2fd65972f"}] - ["Haight Mexican Restaurant is a fantastic and overrated place to sip a glass of expensive wine with your pet toucan." - {:small "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/small.jpg", - :medium "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/med.jpg", - :large "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "foursquare", :foursquare-photo-id "5eac8186-24ef-4369-9b46-3bbec215a9c3", :mayor "cam_saul"}] - ["Rasta's Mexican Sushi is a overrated and well-decorated place to watch the Warriors game with your pet toucan." - {:small "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/small.jpg", - :medium "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/med.jpg", - :large "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/large.jpg"} - {:name "Rasta's Mexican Sushi", :categories ["Mexican" "Sushi"], :phone "415-387-1284", :id "e4912a22-e6ac-4806-8377-6497bf533a21"} - {:service "flare", :username "amy"}] - ["Market St. European Ice Cream Truck is a amazing and groovy place to have a drink on Saturday night." - {:small "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/small.jpg", - :medium "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/med.jpg", - :large "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/large.jpg"} - {:name "Market St. European Ice Cream Truck", :categories ["European" "Ice Cream Truck"], :phone "415-555-4197", :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"} - {:service "facebook", :facebook-photo-id "d7ab7046-43cd-472e-a48d-1b11cd3f7b55", :url "http://facebook.com/photos/d7ab7046-43cd-472e-a48d-1b11cd3f7b55"}] - ["Tenderloin Paleo Hotel & Restaurant is a underappreciated and fantastic place to take visiting friends and relatives after baseball games." - {:small "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/small.jpg", - :medium "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/med.jpg", - :large "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/large.jpg"} - {:name "Tenderloin Paleo Hotel & Restaurant", :categories ["Paleo" "Hotel & Restaurant"], :phone "415-402-1652", :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"} - {:service "flare", :username "lucky_pigeon"}] - ["Lower Pac Heights Cage-Free Coffee House is a well-decorated and amazing place to watch the Warriors game when hungover." - {:small "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/small.jpg", - :medium "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/med.jpg", - :large "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "foursquare", :foursquare-photo-id "b8ef27c8-8dc4-45ff-9485-c130889eaea1", :mayor "joe"}] - ["Polk St. Mexican Coffee House is a world-famous and modern place to pitch an investor in the spring." - {:small "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/small.jpg", - :medium "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/med.jpg", - :large "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "facebook", :facebook-photo-id "3c80ed1f-409b-4cfa-bf78-9bbc8cd8f9f5", :url "http://facebook.com/photos/3c80ed1f-409b-4cfa-bf78-9bbc8cd8f9f5"}] - ["Sunset Homestyle Grill is a amazing and world-famous place to drink a craft beer when hungover." - {:small "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/small.jpg", - :medium "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/med.jpg", - :large "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "yelp", :yelp-photo-id "4ab42195-c3df-49e2-b8e9-20e4f5fae63b", :categories ["Homestyle" "Grill"]}] - ["Marina Modern Bar & Grill is a atmospheric and great place to have breakfast during winter." - {:small "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/small.jpg", - :medium "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/med.jpg", - :large "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/large.jpg"} - {:name "Marina Modern Bar & Grill", :categories ["Modern" "Bar & Grill"], :phone "415-203-8530", :id "806144f1-bb7a-4271-8fcb-fc6550f51676"} - {:service "facebook", :facebook-photo-id "0a3e4b0e-ecd7-4c55-8d5f-2265ad57b02a", :url "http://facebook.com/photos/0a3e4b0e-ecd7-4c55-8d5f-2265ad57b02a"}] - ["Polk St. Mexican Coffee House is a swell and wonderful place to have a after-work cocktail in June." - {:small "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/small.jpg", - :medium "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/med.jpg", - :large "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "twitter", :mentions ["@polk_st._mexican_coffee_house"], :tags ["#mexican" "#coffee" "#house"], :username "amy"}] - ["Nob Hill Korean Taqueria is a decent and modern place to catch a bite to eat on Taco Tuesday." - {:small "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/small.jpg", - :medium "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/med.jpg", - :large "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "yelp", :yelp-photo-id "b0b7027e-5884-48fb-93d4-d11f44c564df", :categories ["Korean" "Taqueria"]}] - ["SF Deep-Dish Eatery is a world-famous and historical place to catch a bite to eat on a Tuesday afternoon." - {:small "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/small.jpg", - :medium "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/med.jpg", - :large "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/large.jpg"} - {:name "SF Deep-Dish Eatery", :categories ["Deep-Dish" "Eatery"], :phone "415-476-9257", :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"} - {:service "foursquare", :foursquare-photo-id "217e84df-2032-4e13-b32d-20745fc36be0", :mayor "amy"}] - ["Sunset Deep-Dish Hotel & Restaurant is a popular and groovy place to have a drink weekend mornings." - {:small "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/small.jpg", - :medium "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/med.jpg", - :large "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/large.jpg"} - {:name "Sunset Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-332-0978", :id "a80745c7-af74-4579-8932-70dd488269e6"} - {:service "twitter", :mentions ["@sunset_deep_dish_hotel_&_restaurant"], :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], :username "lucky_pigeon"}] - ["Polk St. Japanese Liquor Store is a fantastic and delicious place to catch a bite to eat with your pet dog." - {:small "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/small.jpg", - :medium "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/med.jpg", - :large "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/large.jpg"} - {:name "Polk St. Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-726-7986", :id "b57ceac5-328d-4b65-9909-a1f9abc93015"} - {:service "foursquare", :foursquare-photo-id "0928a8d9-8198-4004-ae00-58025bc98a4b", :mayor "mandy"}] - ["Chinatown Paleo Food Truck is a modern and underappreciated place to nurse a hangover weekend mornings." - {:small "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/small.jpg", - :medium "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/med.jpg", - :large "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "facebook", :facebook-photo-id "533e16df-df5c-4368-81fe-ca4c53797f4c", :url "http://facebook.com/photos/533e16df-df5c-4368-81fe-ca4c53797f4c"}] - ["Rasta's Paleo Churros is a well-decorated and underground place to sip a glass of expensive wine in the fall." - {:small "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/small.jpg", - :medium "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/med.jpg", - :large "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "facebook", :facebook-photo-id "71b1c7b4-cf56-4cb1-985a-d97edb8bda7c", :url "http://facebook.com/photos/71b1c7b4-cf56-4cb1-985a-d97edb8bda7c"}] - ["Market St. Gluten-Free Café is a classic and exclusive place to drink a craft beer with friends." - {:small "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/small.jpg", - :medium "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/med.jpg", - :large "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "facebook", :facebook-photo-id "250305eb-9827-43b8-a190-401a7170eb1e", :url "http://facebook.com/photos/250305eb-9827-43b8-a190-401a7170eb1e"}] - ["Lucky's Old-Fashioned Eatery is a underground and delicious place to watch the Warriors game during summer." - {:small "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/small.jpg", - :medium "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/med.jpg", - :large "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/large.jpg"} - {:name "Lucky's Old-Fashioned Eatery", :categories ["Old-Fashioned" "Eatery"], :phone "415-362-2338", :id "71dc221c-6e82-4d06-8709-93293121b1da"} - {:service "foursquare", :foursquare-photo-id "c1759648-8365-48ca-8068-f3ba5fbb32a4", :mayor "mandy"}] - ["Polk St. Japanese Liquor Store is a underappreciated and modern place to catch a bite to eat weekend mornings." - {:small "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/small.jpg", - :medium "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/med.jpg", - :large "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/large.jpg"} - {:name "Polk St. Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-726-7986", :id "b57ceac5-328d-4b65-9909-a1f9abc93015"} - {:service "facebook", :facebook-photo-id "3f7b182d-22b8-401c-939d-bd08cde5a9ac", :url "http://facebook.com/photos/3f7b182d-22b8-401c-939d-bd08cde5a9ac"}] - ["Cam's Mexican Gastro Pub is a great and world-famous place to catch a bite to eat when hungover." - {:small "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/small.jpg", - :medium "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/med.jpg", - :large "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "foursquare", :foursquare-photo-id "6a0d547d-2053-4c14-b35f-f658cfc21f84", :mayor "sameer"}] - ["Lucky's Gluten-Free Gastro Pub is a wonderful and horrible place to have a birthday party with friends." - {:small "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/small.jpg", - :medium "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/med.jpg", - :large "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "facebook", :facebook-photo-id "c5fff3c3-8ea4-42d2-b3dd-29d83c6a9ed2", :url "http://facebook.com/photos/c5fff3c3-8ea4-42d2-b3dd-29d83c6a9ed2"}] - ["Kyle's Chinese Restaurant is a classic and decent place to catch a bite to eat the first Sunday of the month." - {:small "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/small.jpg", - :medium "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/med.jpg", - :large "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "foursquare", :foursquare-photo-id "eff5660c-a3bf-42e5-9406-a9181f3abf41", :mayor "lucky_pigeon"}] - ["Nob Hill Gluten-Free Coffee House is a wonderful and overrated place to meet new friends the second Saturday of the month." - {:small "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/small.jpg", - :medium "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/med.jpg", - :large "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "foursquare", :foursquare-photo-id "901ae47c-e6f2-4511-a20b-b89e2b600239", :mayor "cam_saul"}] - ["Sunset American Churros is a amazing and exclusive place to have a birthday party the second Saturday of the month." - {:small "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/small.jpg", - :medium "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/med.jpg", - :large "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "facebook", :facebook-photo-id "b4f4f18b-fb73-42a5-9628-607f6526a8ca", :url "http://facebook.com/photos/b4f4f18b-fb73-42a5-9628-607f6526a8ca"}] - ["SoMa British Bakery is a atmospheric and underground place to sip Champagne in July." - {:small "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/small.jpg", - :medium "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/med.jpg", - :large "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/large.jpg"} - {:name "SoMa British Bakery", :categories ["British" "Bakery"], :phone "415-909-5728", :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"} - {:service "flare", :username "jessica"}] - ["Tenderloin Cage-Free Sushi is a swell and historical place to people-watch on Thursdays." - {:small "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/small.jpg", - :medium "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/med.jpg", - :large "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "twitter", :mentions ["@tenderloin_cage_free_sushi"], :tags ["#cage-free" "#sushi"], :username "joe"}] - ["Sunset American Churros is a great and atmospheric place to conduct a business meeting with friends." - {:small "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/small.jpg", - :medium "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/med.jpg", - :large "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "yelp", :yelp-photo-id "6e4d9aff-63d3-4b00-9134-a4a3727b9d8d", :categories ["American" "Churros"]}] - ["Rasta's British Food Truck is a fantastic and underground place to sip a glass of expensive wine in June." - {:small "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/small.jpg", - :medium "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/med.jpg", - :large "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/large.jpg"} - {:name "Rasta's British Food Truck", :categories ["British" "Food Truck"], :phone "415-958-9031", :id "b6616c97-01d0-488f-a855-bcd6efe2b899"} - {:service "facebook", :facebook-photo-id "ff353525-8a3c-4510-85ec-c22d5fe5b831", :url "http://facebook.com/photos/ff353525-8a3c-4510-85ec-c22d5fe5b831"}] - ["Haight Gormet Pizzeria is a swell and modern place to have a after-work cocktail weekday afternoons." - {:small "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/small.jpg", - :medium "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/med.jpg", - :large "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/large.jpg"} - {:name "Haight Gormet Pizzeria", :categories ["Gormet" "Pizzeria"], :phone "415-869-2197", :id "0425bdd0-3f57-4108-80e3-78335327355a"} - {:service "facebook", :facebook-photo-id "6387d17f-d000-488b-b9f4-5f808e517f28", :url "http://facebook.com/photos/6387d17f-d000-488b-b9f4-5f808e517f28"}] - ["Haight Chinese Gastro Pub is a modern and great place to meet new friends the second Saturday of the month." - {:small "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/small.jpg", - :medium "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/med.jpg", - :large "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "facebook", :facebook-photo-id "8c4a5b4b-f72b-41bc-8242-3d31df7f175a", :url "http://facebook.com/photos/8c4a5b4b-f72b-41bc-8242-3d31df7f175a"}] - ["Cam's Old-Fashioned Coffee House is a fantastic and overrated place to have brunch in the spring." - {:small "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/small.jpg", - :medium "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/med.jpg", - :large "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "yelp", :yelp-photo-id "0562bf77-bb42-464e-951f-6e18206de08b", :categories ["Old-Fashioned" "Coffee House"]}] - ["SoMa Old-Fashioned Pizzeria is a underappreciated and wonderful place to have a after-work cocktail on a Tuesday afternoon." - {:small "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/small.jpg", - :medium "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/med.jpg", - :large "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "foursquare", :foursquare-photo-id "506df972-9899-40d8-be2d-052a0d32730b", :mayor "joe"}] - ["Sameer's Pizza Liquor Store is a classic and decent place to have a birthday party with your pet dog." - {:small "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/small.jpg", - :medium "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/med.jpg", - :large "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "twitter", :mentions ["@sameers_pizza_liquor_store"], :tags ["#pizza" "#liquor" "#store"], :username "sameer"}] - ["Oakland Afgan Coffee House is a wonderful and historical place to watch the Warriors game Friday nights." - {:small "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/small.jpg", - :medium "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/med.jpg", - :large "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/large.jpg"} - {:name "Oakland Afgan Coffee House", :categories ["Afgan" "Coffee House"], :phone "415-674-0208", :id "dcc9efd9-f34c-4ca1-9a41-386f1130f411"} - {:service "facebook", :facebook-photo-id "d4b18407-5358-43a0-8bee-c53606ceb4b4", :url "http://facebook.com/photos/d4b18407-5358-43a0-8bee-c53606ceb4b4"}] - ["Mission Homestyle Churros is a swell and well-decorated place to sip Champagne with your pet dog." - {:small "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/small.jpg", - :medium "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/med.jpg", - :large "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "yelp", :yelp-photo-id "9d710fa3-1505-43a4-8f41-ea8c188ee172", :categories ["Homestyle" "Churros"]}] - ["Haight Soul Food Hotel & Restaurant is a swell and swell place to have a drink in the spring." - {:small "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/small.jpg", - :medium "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/med.jpg", - :large "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "facebook", :facebook-photo-id "81f9c252-5b76-45f1-baf9-a2d919ee7695", :url "http://facebook.com/photos/81f9c252-5b76-45f1-baf9-a2d919ee7695"}] - ["Marina Japanese Liquor Store is a historical and horrible place to drink a craft beer the second Saturday of the month." - {:small "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/small.jpg", - :medium "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/med.jpg", - :large "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/large.jpg"} - {:name "Marina Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-587-9819", :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"} - {:service "flare", :username "kyle"}] - ["SF British Pop-Up Food Stand is a groovy and popular place to meet new friends weekend evenings." - {:small "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/small.jpg", - :medium "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/med.jpg", - :large "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/large.jpg"} - {:name "SF British Pop-Up Food Stand", :categories ["British" "Pop-Up Food Stand"], :phone "415-441-3725", :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"} - {:service "facebook", :facebook-photo-id "adf58b74-9ac3-4c27-a4a7-b951736b79ae", :url "http://facebook.com/photos/adf58b74-9ac3-4c27-a4a7-b951736b79ae"}] - ["Sameer's GMO-Free Restaurant is a delicious and fantastic place to have breakfast in the spring." - {:small "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/small.jpg", - :medium "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/med.jpg", - :large "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/large.jpg"} - {:name "Sameer's GMO-Free Restaurant", :categories ["GMO-Free" "Restaurant"], :phone "415-128-9430", :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"} - {:service "twitter", :mentions ["@sameers_gmo_free_restaurant"], :tags ["#gmo-free" "#restaurant"], :username "cam_saul"}] - ["Rasta's Paleo Churros is a underappreciated and atmospheric place to catch a bite to eat when hungover." - {:small "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/small.jpg", - :medium "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/med.jpg", - :large "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "flare", :username "tupac"}] - ["SoMa Old-Fashioned Pizzeria is a underground and fantastic place to drink a craft beer when hungover." - {:small "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/small.jpg", - :medium "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/med.jpg", - :large "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "facebook", :facebook-photo-id "98c776ab-0c90-4fa4-a4c6-6f0e19ad6b9e", :url "http://facebook.com/photos/98c776ab-0c90-4fa4-a4c6-6f0e19ad6b9e"}] - ["SoMa Old-Fashioned Pizzeria is a exclusive and underappreciated place to pitch an investor the first Sunday of the month." - {:small "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/small.jpg", - :medium "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/med.jpg", - :large "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "twitter", :mentions ["@soma_old_fashioned_pizzeria"], :tags ["#old-fashioned" "#pizzeria"], :username "bob"}] - ["Oakland American Grill is a groovy and modern place to sip a glass of expensive wine weekend mornings." - {:small "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/small.jpg", - :medium "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/med.jpg", - :large "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "twitter", :mentions ["@oakland_american_grill"], :tags ["#american" "#grill"], :username "cam_saul"}] - ["Polk St. Deep-Dish Hotel & Restaurant is a atmospheric and horrible place to sip a glass of expensive wine weekday afternoons." - {:small "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/small.jpg", - :medium "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/med.jpg", - :large "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "twitter", :mentions ["@polk_st._deep_dish_hotel_&_restaurant"], :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], :username "mandy"}] - ["Haight Chinese Gastro Pub is a world-famous and amazing place to sip Champagne when hungover." - {:small "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/small.jpg", - :medium "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/med.jpg", - :large "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "twitter", :mentions ["@haight_chinese_gastro_pub"], :tags ["#chinese" "#gastro" "#pub"], :username "cam_saul"}] - ["Lucky's Gluten-Free Gastro Pub is a popular and overrated place to watch the Giants game the second Saturday of the month." - {:small "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/small.jpg", - :medium "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/med.jpg", - :large "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "foursquare", :foursquare-photo-id "9543ebb3-8d33-468f-8ba1-980fe43aa09b", :mayor "amy"}] - ["Lucky's Deep-Dish Gastro Pub is a great and family-friendly place to meet new friends on Saturday night." - {:small "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/small.jpg", - :medium "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/med.jpg", - :large "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/large.jpg"} - {:name "Lucky's Deep-Dish Gastro Pub", :categories ["Deep-Dish" "Gastro Pub"], :phone "415-487-4085", :id "0136c454-0968-41cd-a237-ceec5724cab8"} - {:service "twitter", :mentions ["@luckys_deep_dish_gastro_pub"], :tags ["#deep-dish" "#gastro" "#pub"], :username "biggie"}] - ["Alcatraz Pizza Churros is a delicious and classic place to have breakfast on Taco Tuesday." - {:small "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/small.jpg", - :medium "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/med.jpg", - :large "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/large.jpg"} - {:name "Alcatraz Pizza Churros", :categories ["Pizza" "Churros"], :phone "415-754-7867", :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"} - {:service "flare", :username "mandy"}] - ["SoMa Old-Fashioned Pizzeria is a popular and amazing place to take visiting friends and relatives during summer." - {:small "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/small.jpg", - :medium "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/med.jpg", - :large "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "flare", :username "jane"}] - ["SoMa Old-Fashioned Pizzeria is a underappreciated and underappreciated place to people-watch weekend evenings." - {:small "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/small.jpg", - :medium "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/med.jpg", - :large "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "yelp", :yelp-photo-id "cf77c28d-03a5-44d4-9882-4fe4bef60a02", :categories ["Old-Fashioned" "Pizzeria"]}] - ["Tenderloin Cage-Free Sushi is a overrated and historical place to catch a bite to eat the first Sunday of the month." - {:small "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/small.jpg", - :medium "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/med.jpg", - :large "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "twitter", :mentions ["@tenderloin_cage_free_sushi"], :tags ["#cage-free" "#sushi"], :username "mandy"}] - ["Polk St. Red White & Blue Café is a historical and swell place to have a birthday party in the fall." - {:small "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/small.jpg", - :medium "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/med.jpg", - :large "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "yelp", :yelp-photo-id "d84760ec-d55c-486d-9b53-e43a20e0395c", :categories ["Red White & Blue" "Café"]}] - ["Market St. Homestyle Pop-Up Food Stand is a classic and underground place to take a date during winter." - {:small "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/small.jpg", - :medium "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/med.jpg", - :large "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/large.jpg"} - {:name "Market St. Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-213-3030", :id "2d873280-e43d-449e-9940-af96ae7df718"} - {:service "foursquare", :foursquare-photo-id "64feb4b2-a403-4be7-aa96-5c5e5595eba5", :mayor "joe"}] - ["Marina Low-Carb Food Truck is a classic and groovy place to nurse a hangover weekend evenings." - {:small "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/small.jpg", - :medium "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/med.jpg", - :large "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "twitter", :mentions ["@marina_low_carb_food_truck"], :tags ["#low-carb" "#food" "#truck"], :username "cam_saul"}] - ["Marina Modern Bar & Grill is a modern and well-decorated place to watch the Giants game on public holidays." - {:small "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/small.jpg", - :medium "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/med.jpg", - :large "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/large.jpg"} - {:name "Marina Modern Bar & Grill", :categories ["Modern" "Bar & Grill"], :phone "415-203-8530", :id "806144f1-bb7a-4271-8fcb-fc6550f51676"} - {:service "foursquare", :foursquare-photo-id "b53c2c6c-b767-4816-b881-52613ecb438d", :mayor "rasta_toucan"}] - ["Alcatraz Cage-Free Restaurant is a swell and underappreciated place to have a after-work cocktail in the fall." - {:small "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/small.jpg", - :medium "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/med.jpg", - :large "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/large.jpg"} - {:name "Alcatraz Cage-Free Restaurant", :categories ["Cage-Free" "Restaurant"], :phone "415-568-0312", :id "fe0c7f8e-4937-4a76-bda4-44ad89c5231c"} - {:service "foursquare", :foursquare-photo-id "a40cb559-779a-45cb-82d4-82361f7ac9c1", :mayor "jane"}] - ["Kyle's Low-Carb Grill is a exclusive and fantastic place to have a birthday party on Saturday night." - {:small "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/small.jpg", - :medium "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/med.jpg", - :large "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "flare", :username "amy"}] - ["Marina Modern Sushi is a exclusive and fantastic place to conduct a business meeting in the fall." - {:small "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/small.jpg", - :medium "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/med.jpg", - :large "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/large.jpg"} - {:name "Marina Modern Sushi", :categories ["Modern" "Sushi"], :phone "415-393-7672", :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"} - {:service "foursquare", :foursquare-photo-id "4f9300e3-8787-4429-9837-52a8949a920e", :mayor "bob"}] - ["Haight Soul Food Hotel & Restaurant is a world-famous and popular place to nurse a hangover Friday nights." - {:small "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/small.jpg", - :medium "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/med.jpg", - :large "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "flare", :username "kyle"}] - ["Sunset American Churros is a decent and fantastic place to have a drink weekend mornings." - {:small "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/small.jpg", - :medium "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/med.jpg", - :large "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "facebook", :facebook-photo-id "0bbffb73-91f1-4a27-97df-5e0dbae6532b", :url "http://facebook.com/photos/0bbffb73-91f1-4a27-97df-5e0dbae6532b"}] - ["Cam's Mexican Gastro Pub is a family-friendly and acceptable place to people-watch weekend evenings." - {:small "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/small.jpg", - :medium "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/med.jpg", - :large "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "yelp", :yelp-photo-id "fd35ac16-71ef-48f8-8512-2078bda1db30", :categories ["Mexican" "Gastro Pub"]}] - ["Nob Hill Gluten-Free Coffee House is a fantastic and historical place to have a after-work cocktail with your pet toucan." - {:small "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/small.jpg", - :medium "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/med.jpg", - :large "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "facebook", :facebook-photo-id "f9afa193-7158-49d9-9f8c-42542c1e47dd", :url "http://facebook.com/photos/f9afa193-7158-49d9-9f8c-42542c1e47dd"}] - ["Rasta's British Food Truck is a fantastic and underappreciated place to sip a glass of expensive wine when hungover." - {:small "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/small.jpg", - :medium "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/med.jpg", - :large "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/large.jpg"} - {:name "Rasta's British Food Truck", :categories ["British" "Food Truck"], :phone "415-958-9031", :id "b6616c97-01d0-488f-a855-bcd6efe2b899"} - {:service "yelp", :yelp-photo-id "4f557d5b-b69d-48d1-814a-d4cac246e72c", :categories ["British" "Food Truck"]}] - ["Pacific Heights Pizza Bakery is a delicious and amazing place to watch the Giants game weekday afternoons." - {:small "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/small.jpg", - :medium "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/med.jpg", - :large "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "facebook", :facebook-photo-id "0927d96d-e419-49a9-bd61-cb6bb7f46a33", :url "http://facebook.com/photos/0927d96d-e419-49a9-bd61-cb6bb7f46a33"}] - ["Marina No-MSG Sushi is a atmospheric and historical place to sip Champagne on public holidays." - {:small "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/small.jpg", - :medium "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/med.jpg", - :large "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/large.jpg"} - {:name "Marina No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-856-5937", :id "d51013a3-8547-4705-a5f0-cb11d8206481"} - {:service "facebook", :facebook-photo-id "61efe486-455b-4327-995f-235de7d75f5a", :url "http://facebook.com/photos/61efe486-455b-4327-995f-235de7d75f5a"}] - ["Marina Cage-Free Liquor Store is a well-decorated and delicious place to nurse a hangover on a Tuesday afternoon." - {:small "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/small.jpg", - :medium "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/med.jpg", - :large "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "facebook", :facebook-photo-id "f13a16b2-3177-4827-bc81-3abccba5506b", :url "http://facebook.com/photos/f13a16b2-3177-4827-bc81-3abccba5506b"}] - ["Lower Pac Heights Deep-Dish Liquor Store is a horrible and exclusive place to have brunch weekend mornings." - {:small "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/small.jpg", - :medium "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/med.jpg", - :large "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Liquor Store", :categories ["Deep-Dish" "Liquor Store"], :phone "415-497-3039", :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"} - {:service "facebook", :facebook-photo-id "2b761a56-e66d-4cad-9117-ef6b0b633720", :url "http://facebook.com/photos/2b761a56-e66d-4cad-9117-ef6b0b633720"}] - ["Mission British Café is a world-famous and groovy place to pitch an investor on public holidays." - {:small "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/small.jpg", - :medium "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/med.jpg", - :large "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/large.jpg"} - {:name "Mission British Café", :categories ["British" "Café"], :phone "415-715-7004", :id "c99899e3-439c-4444-9dc4-5598632aec8d"} - {:service "facebook", :facebook-photo-id "9b584eeb-9715-4874-8e56-8a69838e6ddf", :url "http://facebook.com/photos/9b584eeb-9715-4874-8e56-8a69838e6ddf"}] - ["Oakland American Grill is a fantastic and well-decorated place to have a birthday party weekday afternoons." - {:small "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/small.jpg", - :medium "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/med.jpg", - :large "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "flare", :username "sameer"}] - ["Kyle's Low-Carb Grill is a overrated and fantastic place to have breakfast with your pet dog." - {:small "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/small.jpg", - :medium "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/med.jpg", - :large "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "foursquare", :foursquare-photo-id "35681dab-4f25-420d-9a65-915d233817f0", :mayor "sameer"}] - ["Nob Hill Gluten-Free Coffee House is a underappreciated and family-friendly place to take a date after baseball games." - {:small "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/small.jpg", - :medium "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/med.jpg", - :large "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "foursquare", :foursquare-photo-id "1714302b-8629-4b18-ab25-2368ad1e4568", :mayor "biggie"}] - ["Alcatraz Cage-Free Restaurant is a underappreciated and groovy place to have a drink when hungover." - {:small "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/small.jpg", - :medium "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/med.jpg", - :large "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/large.jpg"} - {:name "Alcatraz Cage-Free Restaurant", :categories ["Cage-Free" "Restaurant"], :phone "415-568-0312", :id "fe0c7f8e-4937-4a76-bda4-44ad89c5231c"} - {:service "foursquare", :foursquare-photo-id "c1e232ec-e10f-4ad6-b932-84cd854ee3c2", :mayor "amy"}] - ["Kyle's Low-Carb Grill is a decent and well-decorated place to sip a glass of expensive wine in July." - {:small "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/small.jpg", - :medium "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/med.jpg", - :large "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "yelp", :yelp-photo-id "23e11b87-eb0d-48a8-9eb4-0e7b0453f4d7", :categories ["Low-Carb" "Grill"]}] - ["Lower Pac Heights Deep-Dish Liquor Store is a modern and well-decorated place to pitch an investor after baseball games." - {:small "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/small.jpg", - :medium "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/med.jpg", - :large "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Liquor Store", :categories ["Deep-Dish" "Liquor Store"], :phone "415-497-3039", :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"} - {:service "twitter", :mentions ["@lower_pac_heights_deep_dish_liquor_store"], :tags ["#deep-dish" "#liquor" "#store"], :username "sameer"}] - ["Kyle's Chinese Restaurant is a decent and world-famous place to drink a craft beer in the fall." - {:small "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/small.jpg", - :medium "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/med.jpg", - :large "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "twitter", :mentions ["@kyles_chinese_restaurant"], :tags ["#chinese" "#restaurant"], :username "tupac"}] - ["Lucky's Japanese Bar & Grill is a atmospheric and decent place to watch the Warriors game in the spring." - {:small "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/small.jpg", - :medium "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/med.jpg", - :large "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/large.jpg"} - {:name "Lucky's Japanese Bar & Grill", :categories ["Japanese" "Bar & Grill"], :phone "415-816-1300", :id "602d574a-6fd3-44df-9bac-e71ce1ab5eb4"} - {:service "facebook", :facebook-photo-id "f132a200-55f9-4ae8-b61e-4e932287c502", :url "http://facebook.com/photos/f132a200-55f9-4ae8-b61e-4e932287c502"}] - ["Marina Japanese Liquor Store is a fantastic and modern place to have a birthday party on a Tuesday afternoon." - {:small "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/small.jpg", - :medium "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/med.jpg", - :large "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/large.jpg"} - {:name "Marina Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-587-9819", :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"} - {:service "facebook", :facebook-photo-id "3f37d940-c75b-41f2-b13f-e5b0828843af", :url "http://facebook.com/photos/3f37d940-c75b-41f2-b13f-e5b0828843af"}] - ["Kyle's Chinese Restaurant is a great and delicious place to conduct a business meeting on public holidays." - {:small "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/small.jpg", - :medium "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/med.jpg", - :large "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "facebook", :facebook-photo-id "27189121-e22e-4dd6-85da-8880be9c513c", :url "http://facebook.com/photos/27189121-e22e-4dd6-85da-8880be9c513c"}] - ["Haight Soul Food Hotel & Restaurant is a world-famous and atmospheric place to sip a glass of expensive wine Friday nights." - {:small "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/small.jpg", - :medium "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/med.jpg", - :large "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "twitter", :mentions ["@haight_soul_food_hotel_&_restaurant"], :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], :username "cam_saul"}] - ["Haight Soul Food Café is a wonderful and underground place to watch the Warriors game on a Tuesday afternoon." - {:small "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/small.jpg", - :medium "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/med.jpg", - :large "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "foursquare", :foursquare-photo-id "3bbd4357-aba9-4a8e-837c-c57e42dee4e3", :mayor "biggie"}] - ["Sunset Homestyle Grill is a atmospheric and great place to have brunch weekend mornings." - {:small "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/small.jpg", - :medium "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/med.jpg", - :large "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "facebook", :facebook-photo-id "39e3b7a2-6fcf-4e90-b038-fafcc1e528f6", :url "http://facebook.com/photos/39e3b7a2-6fcf-4e90-b038-fafcc1e528f6"}] - ["Sameer's GMO-Free Pop-Up Food Stand is a wonderful and fantastic place to have breakfast the first Sunday of the month." - {:small "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/small.jpg", - :medium "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/med.jpg", - :large "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/large.jpg"} - {:name "Sameer's GMO-Free Pop-Up Food Stand", :categories ["GMO-Free" "Pop-Up Food Stand"], :phone "415-217-7891", :id "a829efc7-7e03-4e73-b072-83d10d1e3953"} - {:service "yelp", :yelp-photo-id "5bfe141b-4680-45de-be48-4eb8cdb8b791", :categories ["GMO-Free" "Pop-Up Food Stand"]}] - ["Cam's Old-Fashioned Coffee House is a modern and family-friendly place to take visiting friends and relatives Friday nights." - {:small "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/small.jpg", - :medium "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/med.jpg", - :large "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "flare", :username "tupac"}] - ["Tenderloin Cage-Free Sushi is a overrated and overrated place to watch the Warriors game weekend mornings." - {:small "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/small.jpg", - :medium "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/med.jpg", - :large "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "flare", :username "mandy"}] - ["Tenderloin Red White & Blue Pizzeria is a atmospheric and wonderful place to conduct a business meeting Friday nights." - {:small "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/small.jpg", - :medium "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/med.jpg", - :large "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/large.jpg"} - {:name "Tenderloin Red White & Blue Pizzeria", :categories ["Red White & Blue" "Pizzeria"], :phone "415-719-8143", :id "eba3dbcd-100a-4f38-a701-e0dec157f437"} - {:service "facebook", :facebook-photo-id "331f4c85-0b7b-4106-94be-df6628e6fb09", :url "http://facebook.com/photos/331f4c85-0b7b-4106-94be-df6628e6fb09"}] - ["Haight Soul Food Pop-Up Food Stand is a overrated and amazing place to catch a bite to eat weekday afternoons." - {:small "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/small.jpg", - :medium "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/med.jpg", - :large "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "foursquare", :foursquare-photo-id "3dcc3e91-1555-476a-9b3b-7834c2f8f1ba", :mayor "biggie"}] - ["Lucky's Old-Fashioned Eatery is a delicious and family-friendly place to drink a craft beer the first Sunday of the month." - {:small "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/small.jpg", - :medium "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/med.jpg", - :large "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/large.jpg"} - {:name "Lucky's Old-Fashioned Eatery", :categories ["Old-Fashioned" "Eatery"], :phone "415-362-2338", :id "71dc221c-6e82-4d06-8709-93293121b1da"} - {:service "twitter", :mentions ["@luckys_old_fashioned_eatery"], :tags ["#old-fashioned" "#eatery"], :username "lucky_pigeon"}] - ["Tenderloin Japanese Ice Cream Truck is a well-decorated and decent place to have breakfast with friends." - {:small "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/small.jpg", - :medium "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/med.jpg", - :large "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/large.jpg"} - {:name "Tenderloin Japanese Ice Cream Truck", :categories ["Japanese" "Ice Cream Truck"], :phone "415-856-0371", :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"} - {:service "twitter", :mentions ["@tenderloin_japanese_ice_cream_truck"], :tags ["#japanese" "#ice" "#cream" "#truck"], :username "amy"}] - ["Oakland American Grill is a wonderful and underappreciated place to watch the Giants game with friends." - {:small "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/small.jpg", - :medium "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/med.jpg", - :large "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "facebook", :facebook-photo-id "b5874d46-0247-4515-bd96-e8cc562c256d", :url "http://facebook.com/photos/b5874d46-0247-4515-bd96-e8cc562c256d"}] - ["Tenderloin Paleo Hotel & Restaurant is a classic and decent place to sip Champagne the first Sunday of the month." - {:small "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/small.jpg", - :medium "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/med.jpg", - :large "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/large.jpg"} - {:name "Tenderloin Paleo Hotel & Restaurant", :categories ["Paleo" "Hotel & Restaurant"], :phone "415-402-1652", :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"} - {:service "facebook", :facebook-photo-id "dafa1931-d938-44cf-bd12-4321d5c22407", :url "http://facebook.com/photos/dafa1931-d938-44cf-bd12-4321d5c22407"}] - ["Rasta's Paleo Café is a well-decorated and exclusive place to sip a glass of expensive wine on a Tuesday afternoon." - {:small "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/small.jpg", - :medium "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/med.jpg", - :large "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/large.jpg"} - {:name "Rasta's Paleo Café", :categories ["Paleo" "Café"], :phone "415-392-6341", :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"} - {:service "twitter", :mentions ["@rastas_paleo_café"], :tags ["#paleo" "#café"], :username "jessica"}] - ["Lower Pac Heights Deep-Dish Ice Cream Truck is a family-friendly and atmospheric place to nurse a hangover on Taco Tuesday." - {:small "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/small.jpg", - :medium "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/med.jpg", - :large "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", :categories ["Deep-Dish" "Ice Cream Truck"], :phone "415-495-1414", :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"} - {:service "foursquare", :foursquare-photo-id "1a06dc7a-c6a4-47e6-a93d-131e99c481da", :mayor "lucky_pigeon"}] - ["Joe's Homestyle Eatery is a popular and atmospheric place to conduct a business meeting on a Tuesday afternoon." - {:small "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/small.jpg", - :medium "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/med.jpg", - :large "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "foursquare", :foursquare-photo-id "416a5da5-6d01-4a16-956d-dcb85ce88bd5", :mayor "joe"}] - ["Lucky's Low-Carb Coffee House is a exclusive and overrated place to pitch an investor on a Tuesday afternoon." - {:small "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/small.jpg", - :medium "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/med.jpg", - :large "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/large.jpg"} - {:name "Lucky's Low-Carb Coffee House", :categories ["Low-Carb" "Coffee House"], :phone "415-145-7107", :id "81b0f944-f0ce-45e5-b84e-a924c441064a"} - {:service "foursquare", :foursquare-photo-id "3b06925f-553a-4117-9864-b3e35d81d9e0", :mayor "tupac"}] - ["Lower Pac Heights Deep-Dish Ice Cream Truck is a delicious and great place to have a drink on Saturday night." - {:small "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/small.jpg", - :medium "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/med.jpg", - :large "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", :categories ["Deep-Dish" "Ice Cream Truck"], :phone "415-495-1414", :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"} - {:service "foursquare", :foursquare-photo-id "06e30d03-9b56-4820-adb4-c0b7ddde578b", :mayor "jane"}] - ["Lower Pac Heights Cage-Free Coffee House is a horrible and swell place to people-watch the first Sunday of the month." - {:small "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/small.jpg", - :medium "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/med.jpg", - :large "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "twitter", :mentions ["@lower_pac_heights_cage_free_coffee_house"], :tags ["#cage-free" "#coffee" "#house"], :username "biggie"}] - ["Marina Cage-Free Liquor Store is a wonderful and acceptable place to watch the Warriors game during winter." - {:small "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/small.jpg", - :medium "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/med.jpg", - :large "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "facebook", :facebook-photo-id "16a05fd8-120e-4f96-9857-4f340611e5f9", :url "http://facebook.com/photos/16a05fd8-120e-4f96-9857-4f340611e5f9"}] - ["SoMa Japanese Churros is a fantastic and swell place to drink a craft beer in the spring." - {:small "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/small.jpg", - :medium "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/med.jpg", - :large "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "facebook", :facebook-photo-id "4022dab6-225b-4677-be1d-7c201233bdee", :url "http://facebook.com/photos/4022dab6-225b-4677-be1d-7c201233bdee"}] - ["Nob Hill Free-Range Ice Cream Truck is a groovy and fantastic place to have brunch on a Tuesday afternoon." - {:small "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/small.jpg", - :medium "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/med.jpg", - :large "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/large.jpg"} - {:name "Nob Hill Free-Range Ice Cream Truck", :categories ["Free-Range" "Ice Cream Truck"], :phone "415-787-4049", :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"} - {:service "facebook", :facebook-photo-id "7e9a5a67-48ab-4a54-821c-1a6f59a3ea92", :url "http://facebook.com/photos/7e9a5a67-48ab-4a54-821c-1a6f59a3ea92"}] - ["SoMa Old-Fashioned Pizzeria is a horrible and horrible place to conduct a business meeting on Taco Tuesday." - {:small "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/small.jpg", - :medium "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/med.jpg", - :large "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "facebook", :facebook-photo-id "a868cdab-32c0-4939-a024-463412457bca", :url "http://facebook.com/photos/a868cdab-32c0-4939-a024-463412457bca"}] - ["Haight Soul Food Pop-Up Food Stand is a fantastic and family-friendly place to take a date with your pet dog." - {:small "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/small.jpg", - :medium "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/med.jpg", - :large "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "twitter", :mentions ["@haight_soul_food_pop_up_food_stand"], :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], :username "amy"}] - ["Pacific Heights Free-Range Eatery is a atmospheric and modern place to nurse a hangover on Saturday night." - {:small "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/small.jpg", - :medium "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/med.jpg", - :large "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "foursquare", :foursquare-photo-id "a3ad3c09-99fc-45e3-b786-0b293eaa525d", :mayor "jane"}] - ["Sameer's GMO-Free Restaurant is a underground and swell place to watch the Warriors game on Thursdays." - {:small "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/small.jpg", - :medium "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/med.jpg", - :large "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/large.jpg"} - {:name "Sameer's GMO-Free Restaurant", :categories ["GMO-Free" "Restaurant"], :phone "415-128-9430", :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"} - {:service "facebook", :facebook-photo-id "f8d9a1ea-707f-4e4d-9a1b-fbc953f50361", :url "http://facebook.com/photos/f8d9a1ea-707f-4e4d-9a1b-fbc953f50361"}] - ["Rasta's European Taqueria is a acceptable and groovy place to have a birthday party Friday nights." - {:small "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/small.jpg", - :medium "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/med.jpg", - :large "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/large.jpg"} - {:name "Rasta's European Taqueria", :categories ["European" "Taqueria"], :phone "415-631-1599", :id "cb472880-ee6e-46e3-bd58-22cf33109aba"} - {:service "facebook", :facebook-photo-id "50e226aa-7b65-450e-9248-040c24bf3577", :url "http://facebook.com/photos/50e226aa-7b65-450e-9248-040c24bf3577"}] - ["Cam's Old-Fashioned Coffee House is a groovy and classic place to take a date weekend evenings." - {:small "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/small.jpg", - :medium "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/med.jpg", - :large "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "yelp", :yelp-photo-id "e6266710-a32b-4da5-8d9d-6b0440596c10", :categories ["Old-Fashioned" "Coffee House"]}] - ["Polk St. Mexican Coffee House is a exclusive and well-decorated place to people-watch weekend evenings." - {:small "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/small.jpg", - :medium "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/med.jpg", - :large "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "twitter", :mentions ["@polk_st._mexican_coffee_house"], :tags ["#mexican" "#coffee" "#house"], :username "sameer"}] - ["Tenderloin Gluten-Free Bar & Grill is a swell and exclusive place to have brunch weekend evenings." - {:small "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/small.jpg", - :medium "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/med.jpg", - :large "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/large.jpg"} - {:name "Tenderloin Gluten-Free Bar & Grill", :categories ["Gluten-Free" "Bar & Grill"], :phone "415-904-0956", :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"} - {:service "foursquare", :foursquare-photo-id "02362879-deac-452e-b656-976edb806e8b", :mayor "rasta_toucan"}] - ["Sunset Homestyle Grill is a world-famous and fantastic place to meet new friends on public holidays." - {:small "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/small.jpg", - :medium "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/med.jpg", - :large "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "facebook", :facebook-photo-id "fc57f41a-9684-4b88-bb8b-9c223eeb47ef", :url "http://facebook.com/photos/fc57f41a-9684-4b88-bb8b-9c223eeb47ef"}] - ["Haight Chinese Gastro Pub is a exclusive and underappreciated place to drink a craft beer the second Saturday of the month." - {:small "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/small.jpg", - :medium "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/med.jpg", - :large "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "twitter", :mentions ["@haight_chinese_gastro_pub"], :tags ["#chinese" "#gastro" "#pub"], :username "bob"}] - ["Haight Soul Food Café is a great and popular place to pitch an investor during winter." - {:small "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/small.jpg", - :medium "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/med.jpg", - :large "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "foursquare", :foursquare-photo-id "dc0709af-da58-4c33-9919-0c3e42e4e0d7", :mayor "rasta_toucan"}] - ["SF Deep-Dish Eatery is a horrible and great place to drink a craft beer on Taco Tuesday." - {:small "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/small.jpg", - :medium "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/med.jpg", - :large "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/large.jpg"} - {:name "SF Deep-Dish Eatery", :categories ["Deep-Dish" "Eatery"], :phone "415-476-9257", :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"} - {:service "foursquare", :foursquare-photo-id "e415f4d4-08f0-4ee0-abab-d2df0bf41fa1", :mayor "cam_saul"}] - ["Pacific Heights Free-Range Eatery is a groovy and historical place to have a birthday party in the spring." - {:small "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/small.jpg", - :medium "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/med.jpg", - :large "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "flare", :username "jane"}] - ["Haight Chinese Gastro Pub is a world-famous and popular place to take visiting friends and relatives the second Saturday of the month." - {:small "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/small.jpg", - :medium "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/med.jpg", - :large "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "foursquare", :foursquare-photo-id "a3de9324-b821-47ec-a07d-36c9c0702400", :mayor "lucky_pigeon"}] - ["Haight Soul Food Pop-Up Food Stand is a great and wonderful place to catch a bite to eat weekend evenings." - {:small "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/small.jpg", - :medium "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/med.jpg", - :large "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "yelp", :yelp-photo-id "6747488f-9287-40f4-b676-0116b0973bec", :categories ["Soul Food" "Pop-Up Food Stand"]}] - ["Pacific Heights Red White & Blue Bar & Grill is a horrible and decent place to watch the Giants game in July." - {:small "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/small.jpg", - :medium "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/med.jpg", - :large "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "yelp", :yelp-photo-id "2e35c934-2f4c-417f-a6ab-56a8bda58b16", :categories ["Red White & Blue" "Bar & Grill"]}] - ["Rasta's Old-Fashioned Pop-Up Food Stand is a exclusive and family-friendly place to conduct a business meeting on public holidays." - {:small "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/small.jpg", - :medium "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/med.jpg", - :large "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/large.jpg"} - {:name "Rasta's Old-Fashioned Pop-Up Food Stand", :categories ["Old-Fashioned" "Pop-Up Food Stand"], :phone "415-942-1875", :id "9fd8b920-a877-4888-86bf-578b2724ac4e"} - {:service "flare", :username "tupac"}] - ["Mission Free-Range Liquor Store is a groovy and delicious place to conduct a business meeting in June." - {:small "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/small.jpg", - :medium "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/med.jpg", - :large "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/large.jpg"} - {:name "Mission Free-Range Liquor Store", :categories ["Free-Range" "Liquor Store"], :phone "415-041-3816", :id "6e665924-8e2c-42ab-af58-23a27f017e37"} - {:service "foursquare", :foursquare-photo-id "9f9fdd2d-a3d1-4bc1-b706-7e94d8ea2042", :mayor "rasta_toucan"}] - ["Kyle's European Churros is a underappreciated and family-friendly place to watch the Giants game during summer." - {:small "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/small.jpg", - :medium "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/med.jpg", - :large "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/large.jpg"} - {:name "Kyle's European Churros", :categories ["European" "Churros"], :phone "415-233-8392", :id "5270240c-6e6e-4512-9344-3dc497d6ea49"} - {:service "twitter", :mentions ["@kyles_european_churros"], :tags ["#european" "#churros"], :username "jane"}] - ["Haight Soul Food Hotel & Restaurant is a classic and decent place to people-watch on Taco Tuesday." - {:small "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/small.jpg", - :medium "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/med.jpg", - :large "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "flare", :username "rasta_toucan"}] - ["SoMa Old-Fashioned Pizzeria is a world-famous and classic place to catch a bite to eat in June." - {:small "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/small.jpg", - :medium "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/med.jpg", - :large "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "yelp", :yelp-photo-id "22e2d196-931a-4251-8fce-a5492c304185", :categories ["Old-Fashioned" "Pizzeria"]}] - ["Haight Chinese Gastro Pub is a modern and underground place to watch the Warriors game weekday afternoons." - {:small "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/small.jpg", - :medium "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/med.jpg", - :large "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "foursquare", :foursquare-photo-id "0211f4dc-c9db-4505-8b9d-fd5abf151ada", :mayor "rasta_toucan"}] - ["Cam's Old-Fashioned Coffee House is a decent and modern place to conduct a business meeting Friday nights." - {:small "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/small.jpg", - :medium "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/med.jpg", - :large "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "foursquare", :foursquare-photo-id "dc22d4ab-1f2a-4ad2-babf-9b5b96cc65d7", :mayor "joe"}] - ["Market St. Gluten-Free Café is a classic and wonderful place to watch the Warriors game in June." - {:small "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/small.jpg", - :medium "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/med.jpg", - :large "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "foursquare", :foursquare-photo-id "a033581b-09b2-487f-b78d-18af543bc0b6", :mayor "rasta_toucan"}] - ["Tenderloin Paleo Hotel & Restaurant is a world-famous and swell place to sip a glass of expensive wine weekend mornings." - {:small "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/small.jpg", - :medium "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/med.jpg", - :large "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/large.jpg"} - {:name "Tenderloin Paleo Hotel & Restaurant", :categories ["Paleo" "Hotel & Restaurant"], :phone "415-402-1652", :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"} - {:service "foursquare", :foursquare-photo-id "44854a6b-c709-4f0f-ab52-f4b5982ec2ee", :mayor "amy"}] - ["Haight Soul Food Sushi is a swell and acceptable place to nurse a hangover on Saturday night." - {:small "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/small.jpg", - :medium "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/med.jpg", - :large "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/large.jpg"} - {:name "Haight Soul Food Sushi", :categories ["Soul Food" "Sushi"], :phone "415-371-8026", :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"} - {:service "foursquare", :foursquare-photo-id "73b07d4f-8de1-4c30-be6e-65e891d60dcd", :mayor "sameer"}] - ["SoMa Japanese Churros is a world-famous and modern place to drink a craft beer when hungover." - {:small "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/small.jpg", - :medium "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/med.jpg", - :large "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "flare", :username "bob"}] - ["Marina Low-Carb Food Truck is a fantastic and decent place to watch the Giants game in the spring." - {:small "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/small.jpg", - :medium "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/med.jpg", - :large "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "facebook", :facebook-photo-id "7888806a-6bcb-43e4-89a6-ee06096d8f6f", :url "http://facebook.com/photos/7888806a-6bcb-43e4-89a6-ee06096d8f6f"}] - ["Rasta's Paleo Churros is a historical and acceptable place to catch a bite to eat on public holidays." - {:small "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/small.jpg", - :medium "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/med.jpg", - :large "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "yelp", :yelp-photo-id "e63bccf1-3d28-46c5-a3ca-7b8b8063a785", :categories ["Paleo" "Churros"]}] - ["Sameer's GMO-Free Pop-Up Food Stand is a wonderful and horrible place to drink a craft beer on Thursdays." - {:small "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/small.jpg", - :medium "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/med.jpg", - :large "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/large.jpg"} - {:name "Sameer's GMO-Free Pop-Up Food Stand", :categories ["GMO-Free" "Pop-Up Food Stand"], :phone "415-217-7891", :id "a829efc7-7e03-4e73-b072-83d10d1e3953"} - {:service "foursquare", :foursquare-photo-id "2ae13e5f-bfef-46cb-8bfb-f568f5fa383d", :mayor "amy"}] - ["Marina Cage-Free Liquor Store is a delicious and acceptable place to pitch an investor on Taco Tuesday." - {:small "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/small.jpg", - :medium "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/med.jpg", - :large "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "yelp", :yelp-photo-id "03bfbbbc-ca84-459a-a81e-3a2e08986052", :categories ["Cage-Free" "Liquor Store"]}] - ["Mission British Café is a decent and decent place to nurse a hangover after baseball games." - {:small "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/small.jpg", - :medium "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/med.jpg", - :large "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/large.jpg"} - {:name "Mission British Café", :categories ["British" "Café"], :phone "415-715-7004", :id "c99899e3-439c-4444-9dc4-5598632aec8d"} - {:service "yelp", :yelp-photo-id "4b790ee2-1c3f-4ee6-bd5d-9debea0377e1", :categories ["British" "Café"]}] - ["Sameer's Pizza Liquor Store is a great and popular place to nurse a hangover during summer." - {:small "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/small.jpg", - :medium "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/med.jpg", - :large "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "foursquare", :foursquare-photo-id "a8cc052d-7c49-4c06-b81d-81d5208ed90c", :mayor "jessica"}] - ["Pacific Heights Irish Grill is a overrated and underground place to people-watch in June." - {:small "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/small.jpg", - :medium "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/med.jpg", - :large "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/large.jpg"} - {:name "Pacific Heights Irish Grill", :categories ["Irish" "Grill"], :phone "415-491-2202", :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"} - {:service "foursquare", :foursquare-photo-id "5d6e0806-1f8c-43c7-84c4-2209c132d438", :mayor "mandy"}] - ["SoMa Old-Fashioned Pizzeria is a exclusive and acceptable place to watch the Giants game in June." - {:small "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/small.jpg", - :medium "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/med.jpg", - :large "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "foursquare", :foursquare-photo-id "5b08d58b-61b1-464a-8380-0bf9d846a9be", :mayor "sameer"}] - ["Mission Chinese Liquor Store is a swell and well-decorated place to catch a bite to eat during summer." - {:small "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/small.jpg", - :medium "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/med.jpg", - :large "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/large.jpg"} - {:name "Mission Chinese Liquor Store", :categories ["Chinese" "Liquor Store"], :phone "415-906-6919", :id "00132b5b-31fc-46f0-a288-f547f23477ee"} - {:service "twitter", :mentions ["@mission_chinese_liquor_store"], :tags ["#chinese" "#liquor" "#store"], :username "lucky_pigeon"}] - ["Haight Soul Food Sushi is a swell and underappreciated place to watch the Giants game in the spring." - {:small "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/small.jpg", - :medium "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/med.jpg", - :large "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/large.jpg"} - {:name "Haight Soul Food Sushi", :categories ["Soul Food" "Sushi"], :phone "415-371-8026", :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"} - {:service "yelp", :yelp-photo-id "4043626c-1296-45f8-ba01-eca54642defa", :categories ["Soul Food" "Sushi"]}] - ["Market St. Homestyle Pop-Up Food Stand is a amazing and classic place to sip Champagne Friday nights." - {:small "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/small.jpg", - :medium "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/med.jpg", - :large "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/large.jpg"} - {:name "Market St. Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-213-3030", :id "2d873280-e43d-449e-9940-af96ae7df718"} - {:service "yelp", :yelp-photo-id "9b425a8e-9bcb-40ce-98df-52e8c6e47978", :categories ["Homestyle" "Pop-Up Food Stand"]}] - ["Polk St. Deep-Dish Hotel & Restaurant is a well-decorated and fantastic place to take a date in June." - {:small "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/small.jpg", - :medium "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/med.jpg", - :large "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "facebook", :facebook-photo-id "cd159f6b-6c5d-400e-90e9-c667d40cea43", :url "http://facebook.com/photos/cd159f6b-6c5d-400e-90e9-c667d40cea43"}] - ["Pacific Heights Red White & Blue Bar & Grill is a decent and family-friendly place to have a drink on Saturday night." - {:small "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/small.jpg", - :medium "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/med.jpg", - :large "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "twitter", :mentions ["@pacific_heights_red_white_&_blue_bar_&_grill"], :tags ["#red" "#white" "#&" "#blue" "#bar" "#&" "#grill"], :username "cam_saul"}] - ["Haight European Grill is a wonderful and horrible place to sip Champagne with your pet dog." - {:small "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/small.jpg", - :medium "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/med.jpg", - :large "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/large.jpg"} - {:name "Haight European Grill", :categories ["European" "Grill"], :phone "415-191-2778", :id "7e6281f7-5b17-4056-ada0-85453247bc8f"} - {:service "foursquare", :foursquare-photo-id "bad6706f-9387-4946-a65f-872b5055638b", :mayor "tupac"}] - ["Cam's Old-Fashioned Coffee House is a underappreciated and family-friendly place to have a drink with friends." - {:small "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/small.jpg", - :medium "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/med.jpg", - :large "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "twitter", :mentions ["@cams_old_fashioned_coffee_house"], :tags ["#old-fashioned" "#coffee" "#house"], :username "rasta_toucan"}] - ["Cam's Old-Fashioned Coffee House is a exclusive and fantastic place to have a birthday party weekend evenings." - {:small "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/small.jpg", - :medium "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/med.jpg", - :large "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "yelp", :yelp-photo-id "dcc28cfa-c4aa-4c03-8985-48b72c682e06", :categories ["Old-Fashioned" "Coffee House"]}] - ["Mission Homestyle Churros is a well-decorated and exclusive place to have a after-work cocktail the first Sunday of the month." - {:small "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/small.jpg", - :medium "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/med.jpg", - :large "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "flare", :username "cam_saul"}] - ["Alcatraz Pizza Churros is a groovy and underappreciated place to take visiting friends and relatives on Taco Tuesday." - {:small "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/small.jpg", - :medium "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/med.jpg", - :large "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/large.jpg"} - {:name "Alcatraz Pizza Churros", :categories ["Pizza" "Churros"], :phone "415-754-7867", :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"} - {:service "yelp", :yelp-photo-id "161da098-e1d5-43d2-9ca9-f10ca0b48008", :categories ["Pizza" "Churros"]}] - ["Polk St. Deep-Dish Hotel & Restaurant is a modern and swell place to have a birthday party on Saturday night." - {:small "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/small.jpg", - :medium "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/med.jpg", - :large "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "foursquare", :foursquare-photo-id "a8cf3fba-ecc4-46b5-97de-35f953def90e", :mayor "bob"}] - ["Rasta's Mexican Sushi is a swell and horrible place to conduct a business meeting in June." - {:small "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/small.jpg", - :medium "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/med.jpg", - :large "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/large.jpg"} - {:name "Rasta's Mexican Sushi", :categories ["Mexican" "Sushi"], :phone "415-387-1284", :id "e4912a22-e6ac-4806-8377-6497bf533a21"} - {:service "yelp", :yelp-photo-id "7679858d-2f30-4af5-b620-e406eb0b2f73", :categories ["Mexican" "Sushi"]}] - ["Haight Chinese Gastro Pub is a underappreciated and overrated place to take a date weekend evenings." - {:small "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/small.jpg", - :medium "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/med.jpg", - :large "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "twitter", :mentions ["@haight_chinese_gastro_pub"], :tags ["#chinese" "#gastro" "#pub"], :username "tupac"}] - ["Alcatraz Modern Eatery is a underground and underground place to meet new friends on a Tuesday afternoon." - {:small "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/small.jpg", - :medium "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/med.jpg", - :large "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/large.jpg"} - {:name "Alcatraz Modern Eatery", :categories ["Modern" "Eatery"], :phone "415-899-2965", :id "bbfafaac-e825-4c4f-8655-f5e697148d9c"} - {:service "facebook", :facebook-photo-id "ab85f630-7d6d-445f-9848-48461b588909", :url "http://facebook.com/photos/ab85f630-7d6d-445f-9848-48461b588909"}] - ["Haight Soul Food Café is a amazing and fantastic place to drink a craft beer with your pet dog." - {:small "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/small.jpg", - :medium "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/med.jpg", - :large "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "facebook", :facebook-photo-id "050b4f87-87f7-45c2-aeb7-d02cae41b076", :url "http://facebook.com/photos/050b4f87-87f7-45c2-aeb7-d02cae41b076"}] - ["Lucky's Gluten-Free Café is a swell and horrible place to watch the Giants game with your pet dog." - {:small "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/small.jpg", - :medium "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/med.jpg", - :large "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/large.jpg"} - {:name "Lucky's Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-740-2328", :id "379af987-ad40-4a93-88a6-0233e1c14649"} - {:service "foursquare", :foursquare-photo-id "f63d629b-0e13-4c23-8cc8-6b08ca2e8213", :mayor "tupac"}] - ["Joe's Modern Coffee House is a exclusive and exclusive place to sip Champagne with your pet dog." - {:small "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/small.jpg", - :medium "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/med.jpg", - :large "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/large.jpg"} - {:name "Joe's Modern Coffee House", :categories ["Modern" "Coffee House"], :phone "415-331-5269", :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"} - {:service "yelp", :yelp-photo-id "ff896124-cf74-44f8-ab68-631810f8bbff", :categories ["Modern" "Coffee House"]}] - ["Oakland Low-Carb Bakery is a classic and groovy place to have breakfast on Taco Tuesday." - {:small "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/small.jpg", - :medium "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/med.jpg", - :large "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/large.jpg"} - {:name "Oakland Low-Carb Bakery", :categories ["Low-Carb" "Bakery"], :phone "415-546-0101", :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"} - {:service "foursquare", :foursquare-photo-id "8608a711-9a31-4f23-a2b5-01de2b554df0", :mayor "lucky_pigeon"}] - ["Mission Homestyle Churros is a exclusive and popular place to have a birthday party Friday nights." - {:small "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/small.jpg", - :medium "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/med.jpg", - :large "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "foursquare", :foursquare-photo-id "b9f30495-8334-43b1-bcbd-e3b9e8cab52a", :mayor "cam_saul"}] - ["Haight Mexican Restaurant is a swell and exclusive place to meet new friends with your pet dog." - {:small "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/small.jpg", - :medium "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/med.jpg", - :large "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "twitter", :mentions ["@haight_mexican_restaurant"], :tags ["#mexican" "#restaurant"], :username "rasta_toucan"}] - ["Sameer's GMO-Free Pop-Up Food Stand is a great and decent place to have breakfast in June." - {:small "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/small.jpg", - :medium "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/med.jpg", - :large "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/large.jpg"} - {:name "Sameer's GMO-Free Pop-Up Food Stand", :categories ["GMO-Free" "Pop-Up Food Stand"], :phone "415-217-7891", :id "a829efc7-7e03-4e73-b072-83d10d1e3953"} - {:service "twitter", :mentions ["@sameers_gmo_free_pop_up_food_stand"], :tags ["#gmo-free" "#pop-up" "#food" "#stand"], :username "bob"}] - ["Cam's Old-Fashioned Coffee House is a delicious and modern place to people-watch on Taco Tuesday." - {:small "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/small.jpg", - :medium "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/med.jpg", - :large "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-868-2973", :id "27592c2b-e682-44bb-be28-8e9a622becca"} - {:service "foursquare", :foursquare-photo-id "4549eb5a-9e17-4b08-bfb3-44acfcc9d494", :mayor "tupac"}] - ["Cam's Mexican Gastro Pub is a great and swell place to drink a craft beer in June." - {:small "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/small.jpg", - :medium "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/med.jpg", - :large "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "flare", :username "jane"}] - ["Sunset Homestyle Grill is a overrated and underappreciated place to nurse a hangover in the fall." - {:small "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/small.jpg", - :medium "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/med.jpg", - :large "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "twitter", :mentions ["@sunset_homestyle_grill"], :tags ["#homestyle" "#grill"], :username "amy"}] - ["Polk St. Red White & Blue Café is a delicious and swell place to conduct a business meeting on Thursdays." - {:small "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/small.jpg", - :medium "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/med.jpg", - :large "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "facebook", :facebook-photo-id "01ba3c6f-4360-406a-b1d3-5e6ca8d28a5a", :url "http://facebook.com/photos/01ba3c6f-4360-406a-b1d3-5e6ca8d28a5a"}] - ["Haight European Grill is a swell and overrated place to watch the Giants game with your pet dog." - {:small "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/small.jpg", - :medium "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/med.jpg", - :large "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/large.jpg"} - {:name "Haight European Grill", :categories ["European" "Grill"], :phone "415-191-2778", :id "7e6281f7-5b17-4056-ada0-85453247bc8f"} - {:service "facebook", :facebook-photo-id "42762b62-7cff-4a54-a0f7-f16c1f42d81c", :url "http://facebook.com/photos/42762b62-7cff-4a54-a0f7-f16c1f42d81c"}] - ["Haight Soul Food Pop-Up Food Stand is a amazing and world-famous place to sip a glass of expensive wine weekend evenings." - {:small "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/small.jpg", - :medium "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/med.jpg", - :large "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "twitter", :mentions ["@haight_soul_food_pop_up_food_stand"], :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], :username "sameer"}] - ["Tenderloin Red White & Blue Pizzeria is a decent and underappreciated place to have a drink on Taco Tuesday." - {:small "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/small.jpg", - :medium "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/med.jpg", - :large "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/large.jpg"} - {:name "Tenderloin Red White & Blue Pizzeria", :categories ["Red White & Blue" "Pizzeria"], :phone "415-719-8143", :id "eba3dbcd-100a-4f38-a701-e0dec157f437"} - {:service "twitter", :mentions ["@tenderloin_red_white_&_blue_pizzeria"], :tags ["#red" "#white" "#&" "#blue" "#pizzeria"], :username "jessica"}] - ["Sunset Homestyle Grill is a amazing and wonderful place to have brunch on a Tuesday afternoon." - {:small "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/small.jpg", - :medium "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/med.jpg", - :large "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "twitter", :mentions ["@sunset_homestyle_grill"], :tags ["#homestyle" "#grill"], :username "jane"}] - ["Lucky's Old-Fashioned Eatery is a decent and acceptable place to have a birthday party on public holidays." - {:small "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/small.jpg", - :medium "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/med.jpg", - :large "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/large.jpg"} - {:name "Lucky's Old-Fashioned Eatery", :categories ["Old-Fashioned" "Eatery"], :phone "415-362-2338", :id "71dc221c-6e82-4d06-8709-93293121b1da"} - {:service "foursquare", :foursquare-photo-id "b555bae0-96f0-492f-807d-484460d33f62", :mayor "sameer"}] - ["Pacific Heights Free-Range Eatery is a swell and swell place to have a birthday party the first Sunday of the month." - {:small "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/small.jpg", - :medium "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/med.jpg", - :large "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "foursquare", :foursquare-photo-id "bb3679fc-46fa-4b28-b0d2-291b301c67c1", :mayor "rasta_toucan"}] - ["Mission Homestyle Churros is a swell and great place to have a birthday party weekend evenings." - {:small "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/small.jpg", - :medium "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/med.jpg", - :large "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "foursquare", :foursquare-photo-id "76baeb06-f79c-43c4-ae15-108b69983aa3", :mayor "mandy"}] - ["Mission Japanese Coffee House is a classic and wonderful place to watch the Warriors game in the spring." - {:small "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/small.jpg", - :medium "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/med.jpg", - :large "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/large.jpg"} - {:name "Mission Japanese Coffee House", :categories ["Japanese" "Coffee House"], :phone "415-561-0506", :id "60dd274e-0cbf-4521-946d-8a4e0f151150"} - {:service "yelp", :yelp-photo-id "35f2e21d-b05f-4e7c-adfe-e8d70ecd478e", :categories ["Japanese" "Coffee House"]}] - ["Kyle's Free-Range Taqueria is a popular and modern place to have a birthday party in June." - {:small "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/small.jpg", - :medium "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/med.jpg", - :large "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/large.jpg"} - {:name "Kyle's Free-Range Taqueria", :categories ["Free-Range" "Taqueria"], :phone "415-201-7832", :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"} - {:service "yelp", :yelp-photo-id "f1bc8db8-9cd5-4664-b6d9-440618790ffc", :categories ["Free-Range" "Taqueria"]}] - ["Haight Soul Food Hotel & Restaurant is a wonderful and amazing place to meet new friends with friends." - {:small "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/small.jpg", - :medium "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/med.jpg", - :large "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "flare", :username "mandy"}] - ["SoMa Japanese Churros is a fantastic and world-famous place to watch the Warriors game when hungover." - {:small "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/small.jpg", - :medium "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/med.jpg", - :large "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "twitter", :mentions ["@soma_japanese_churros"], :tags ["#japanese" "#churros"], :username "tupac"}] - ["Pacific Heights Pizza Bakery is a overrated and exclusive place to take visiting friends and relatives on public holidays." - {:small "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/small.jpg", - :medium "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/med.jpg", - :large "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "yelp", :yelp-photo-id "da8703eb-dd7c-4559-b39c-e2a6fff91b92", :categories ["Pizza" "Bakery"]}] - ["Mission British Café is a decent and swell place to drink a craft beer in June." - {:small "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/small.jpg", - :medium "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/med.jpg", - :large "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/large.jpg"} - {:name "Mission British Café", :categories ["British" "Café"], :phone "415-715-7004", :id "c99899e3-439c-4444-9dc4-5598632aec8d"} - {:service "twitter", :mentions ["@mission_british_café"], :tags ["#british" "#café"], :username "lucky_pigeon"}] - ["Oakland American Grill is a amazing and underground place to have brunch on Saturday night." - {:small "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/small.jpg", - :medium "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/med.jpg", - :large "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "flare", :username "mandy"}] - ["Pacific Heights Free-Range Eatery is a great and modern place to take visiting friends and relatives on a Tuesday afternoon." - {:small "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/small.jpg", - :medium "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/med.jpg", - :large "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "twitter", :mentions ["@pacific_heights_free_range_eatery"], :tags ["#free-range" "#eatery"], :username "biggie"}] - ["SoMa Old-Fashioned Pizzeria is a groovy and delicious place to nurse a hangover when hungover." - {:small "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/small.jpg", - :medium "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/med.jpg", - :large "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "flare", :username "joe"}] - ["Haight Mexican Restaurant is a historical and horrible place to sip Champagne weekday afternoons." - {:small "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/small.jpg", - :medium "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/med.jpg", - :large "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "twitter", :mentions ["@haight_mexican_restaurant"], :tags ["#mexican" "#restaurant"], :username "amy"}] - ["Nob Hill Korean Taqueria is a overrated and classic place to pitch an investor weekend mornings." - {:small "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/small.jpg", - :medium "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/med.jpg", - :large "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "twitter", :mentions ["@nob_hill_korean_taqueria"], :tags ["#korean" "#taqueria"], :username "jessica"}] - ["Joe's Modern Coffee House is a acceptable and fantastic place to watch the Giants game in the spring." - {:small "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/small.jpg", - :medium "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/med.jpg", - :large "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/large.jpg"} - {:name "Joe's Modern Coffee House", :categories ["Modern" "Coffee House"], :phone "415-331-5269", :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"} - {:service "foursquare", :foursquare-photo-id "e00287d6-3633-42a1-a096-7756abdc25fa", :mayor "joe"}] - ["Kyle's Chinese Restaurant is a exclusive and great place to have a after-work cocktail on Saturday night." - {:small "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/small.jpg", - :medium "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/med.jpg", - :large "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "twitter", :mentions ["@kyles_chinese_restaurant"], :tags ["#chinese" "#restaurant"], :username "jessica"}] - ["Pacific Heights Soul Food Coffee House is a fantastic and great place to catch a bite to eat in June." - {:small "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/small.jpg", - :medium "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/med.jpg", - :large "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/large.jpg"} - {:name "Pacific Heights Soul Food Coffee House", :categories ["Soul Food" "Coffee House"], :phone "415-838-3464", :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"} - {:service "facebook", :facebook-photo-id "a909c6c2-faee-4994-9f99-a922f40d64ea", :url "http://facebook.com/photos/a909c6c2-faee-4994-9f99-a922f40d64ea"}] - ["Chinatown Paleo Food Truck is a underground and well-decorated place to drink a craft beer weekend evenings." - {:small "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/small.jpg", - :medium "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/med.jpg", - :large "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "twitter", :mentions ["@chinatown_paleo_food_truck"], :tags ["#paleo" "#food" "#truck"], :username "joe"}] - ["Lucky's Low-Carb Coffee House is a delicious and atmospheric place to take a date during winter." - {:small "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/small.jpg", - :medium "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/med.jpg", - :large "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/large.jpg"} - {:name "Lucky's Low-Carb Coffee House", :categories ["Low-Carb" "Coffee House"], :phone "415-145-7107", :id "81b0f944-f0ce-45e5-b84e-a924c441064a"} - {:service "flare", :username "tupac"}] - ["Sunset American Churros is a underground and acceptable place to have a drink in the spring." - {:small "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/small.jpg", - :medium "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/med.jpg", - :large "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "facebook", :facebook-photo-id "33e0c40f-43b2-4f59-8539-9ff952ce06c3", :url "http://facebook.com/photos/33e0c40f-43b2-4f59-8539-9ff952ce06c3"}] - ["Kyle's Chinese Restaurant is a historical and atmospheric place to watch the Warriors game in June." - {:small "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/small.jpg", - :medium "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/med.jpg", - :large "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "facebook", :facebook-photo-id "36ecd1a2-0282-422c-995a-664668a4cb80", :url "http://facebook.com/photos/36ecd1a2-0282-422c-995a-664668a4cb80"}] - ["Polk St. Korean Taqueria is a overrated and popular place to watch the Warriors game with your pet dog." - {:small "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/small.jpg", - :medium "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/med.jpg", - :large "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "flare", :username "amy"}] - ["Marina Japanese Liquor Store is a underappreciated and fantastic place to conduct a business meeting in June." - {:small "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/small.jpg", - :medium "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/med.jpg", - :large "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/large.jpg"} - {:name "Marina Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-587-9819", :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"} - {:service "twitter", :mentions ["@marina_japanese_liquor_store"], :tags ["#japanese" "#liquor" "#store"], :username "jane"}] - ["Polk St. Red White & Blue Café is a underground and horrible place to nurse a hangover on Taco Tuesday." - {:small "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/small.jpg", - :medium "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/med.jpg", - :large "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "facebook", :facebook-photo-id "54f31306-b4ce-46f6-b30e-37dc2f10fc18", :url "http://facebook.com/photos/54f31306-b4ce-46f6-b30e-37dc2f10fc18"}] - ["SoMa Old-Fashioned Pizzeria is a groovy and amazing place to have brunch with your pet toucan." - {:small "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/small.jpg", - :medium "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/med.jpg", - :large "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "yelp", :yelp-photo-id "c8f35c36-0f19-4318-a0e4-88d96b5b72eb", :categories ["Old-Fashioned" "Pizzeria"]}] - ["Haight Soul Food Hotel & Restaurant is a popular and modern place to take visiting friends and relatives weekday afternoons." - {:small "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/small.jpg", - :medium "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/med.jpg", - :large "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "yelp", :yelp-photo-id "d753f0de-f1db-4cf0-9260-ad3eeee4aa9c", :categories ["Soul Food" "Hotel & Restaurant"]}] - ["Marina Low-Carb Food Truck is a underappreciated and modern place to take a date with friends." - {:small "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/small.jpg", - :medium "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/med.jpg", - :large "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "foursquare", :foursquare-photo-id "a5c87b6a-a1e6-4abe-932f-90dde0e8125b", :mayor "tupac"}] - ["Mission Soul Food Pizzeria is a amazing and world-famous place to have a after-work cocktail in the spring." - {:small "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/small.jpg", - :medium "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/med.jpg", - :large "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/large.jpg"} - {:name "Mission Soul Food Pizzeria", :categories ["Soul Food" "Pizzeria"], :phone "415-437-3479", :id "9905fe61-44cb-4626-843b-5d725c7949bb"} - {:service "yelp", :yelp-photo-id "728c253f-8576-4e91-a0f7-8f4409ef3ea3", :categories ["Soul Food" "Pizzeria"]}] - ["Cam's Old-Fashioned Coffee House is a delicious and overrated place to have a birthday party weekend mornings." - {:small "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/small.jpg", - :medium "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/med.jpg", - :large "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-871-9473", :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"} - {:service "twitter", :mentions ["@cams_old_fashioned_coffee_house"], :tags ["#old-fashioned" "#coffee" "#house"], :username "tupac"}] - ["Haight European Grill is a family-friendly and wonderful place to sip Champagne on Saturday night." - {:small "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/small.jpg", - :medium "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/med.jpg", - :large "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/large.jpg"} - {:name "Haight European Grill", :categories ["European" "Grill"], :phone "415-191-2778", :id "7e6281f7-5b17-4056-ada0-85453247bc8f"} - {:service "twitter", :mentions ["@haight_european_grill"], :tags ["#european" "#grill"], :username "lucky_pigeon"}] - ["Polk St. Deep-Dish Hotel & Restaurant is a world-famous and popular place to sip Champagne with your pet dog." - {:small "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/small.jpg", - :medium "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/med.jpg", - :large "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "flare", :username "mandy"}] - ["SF Afgan Restaurant is a horrible and delicious place to sip a glass of expensive wine the first Sunday of the month." - {:small "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/small.jpg", - :medium "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/med.jpg", - :large "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/large.jpg"} - {:name "SF Afgan Restaurant", :categories ["Afgan" "Restaurant"], :phone "415-451-4697", :id "66ccc68a-db9a-470c-a17b-7764d23daced"} - {:service "twitter", :mentions ["@sf_afgan_restaurant"], :tags ["#afgan" "#restaurant"], :username "rasta_toucan"}] - ["Tenderloin Gluten-Free Bar & Grill is a exclusive and wonderful place to meet new friends on Thursdays." - {:small "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/small.jpg", - :medium "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/med.jpg", - :large "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/large.jpg"} - {:name "Tenderloin Gluten-Free Bar & Grill", :categories ["Gluten-Free" "Bar & Grill"], :phone "415-904-0956", :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"} - {:service "facebook", :facebook-photo-id "f073cb47-002c-4db7-b80a-c5ed327d0ac9", :url "http://facebook.com/photos/f073cb47-002c-4db7-b80a-c5ed327d0ac9"}] - ["Pacific Heights Irish Grill is a overrated and popular place to catch a bite to eat during winter." - {:small "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/small.jpg", - :medium "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/med.jpg", - :large "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/large.jpg"} - {:name "Pacific Heights Irish Grill", :categories ["Irish" "Grill"], :phone "415-491-2202", :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"} - {:service "flare", :username "lucky_pigeon"}] - ["Haight Soul Food Café is a horrible and underground place to take visiting friends and relatives on Thursdays." - {:small "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/small.jpg", - :medium "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/med.jpg", - :large "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "flare", :username "jane"}] - ["Marina Cage-Free Liquor Store is a delicious and family-friendly place to watch the Warriors game after baseball games." - {:small "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/small.jpg", - :medium "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/med.jpg", - :large "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "twitter", :mentions ["@marina_cage_free_liquor_store"], :tags ["#cage-free" "#liquor" "#store"], :username "bob"}] - ["Lower Pac Heights Deep-Dish Ice Cream Truck is a classic and underground place to take a date with friends." - {:small "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/small.jpg", - :medium "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/med.jpg", - :large "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", :categories ["Deep-Dish" "Ice Cream Truck"], :phone "415-495-1414", :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"} - {:service "flare", :username "rasta_toucan"}] - ["SoMa Old-Fashioned Pizzeria is a swell and acceptable place to sip a glass of expensive wine on a Tuesday afternoon." - {:small "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/small.jpg", - :medium "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/med.jpg", - :large "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "twitter", :mentions ["@soma_old_fashioned_pizzeria"], :tags ["#old-fashioned" "#pizzeria"], :username "biggie"}] - ["SF British Pop-Up Food Stand is a swell and popular place to people-watch on Thursdays." - {:small "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/small.jpg", - :medium "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/med.jpg", - :large "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/large.jpg"} - {:name "SF British Pop-Up Food Stand", :categories ["British" "Pop-Up Food Stand"], :phone "415-441-3725", :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"} - {:service "facebook", :facebook-photo-id "98c2e661-c20e-41df-967a-b65635e34031", :url "http://facebook.com/photos/98c2e661-c20e-41df-967a-b65635e34031"}] - ["Lower Pac Heights Cage-Free Coffee House is a classic and fantastic place to take visiting friends and relatives with your pet dog." - {:small "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/small.jpg", - :medium "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/med.jpg", - :large "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "flare", :username "rasta_toucan"}] - ["Haight Mexican Restaurant is a amazing and underappreciated place to nurse a hangover weekday afternoons." - {:small "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/small.jpg", - :medium "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/med.jpg", - :large "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "twitter", :mentions ["@haight_mexican_restaurant"], :tags ["#mexican" "#restaurant"], :username "rasta_toucan"}] - ["Cam's Old-Fashioned Coffee House is a delicious and great place to pitch an investor when hungover." - {:small "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/small.jpg", - :medium "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/med.jpg", - :large "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-868-2973", :id "27592c2b-e682-44bb-be28-8e9a622becca"} - {:service "facebook", :facebook-photo-id "81a3711b-d0c6-4100-b3f7-b18f67613a09", :url "http://facebook.com/photos/81a3711b-d0c6-4100-b3f7-b18f67613a09"}] - ["Tenderloin Gormet Restaurant is a modern and decent place to have a after-work cocktail during winter." - {:small "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/small.jpg", - :medium "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/med.jpg", - :large "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/large.jpg"} - {:name "Tenderloin Gormet Restaurant", :categories ["Gormet" "Restaurant"], :phone "415-127-4197", :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"} - {:service "flare", :username "joe"}] - ["Chinatown Paleo Food Truck is a classic and underground place to watch the Giants game in the fall." - {:small "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/small.jpg", - :medium "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/med.jpg", - :large "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "twitter", :mentions ["@chinatown_paleo_food_truck"], :tags ["#paleo" "#food" "#truck"], :username "joe"}] - ["Haight Soul Food Hotel & Restaurant is a family-friendly and amazing place to watch the Warriors game in June." - {:small "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/small.jpg", - :medium "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/med.jpg", - :large "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "twitter", :mentions ["@haight_soul_food_hotel_&_restaurant"], :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], :username "bob"}] - ["Haight Soul Food Pop-Up Food Stand is a decent and underground place to conduct a business meeting in the spring." - {:small "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/small.jpg", - :medium "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/med.jpg", - :large "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "flare", :username "kyle"}] - ["Joe's Modern Coffee House is a underappreciated and delicious place to take a date in July." - {:small "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/small.jpg", - :medium "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/med.jpg", - :large "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/large.jpg"} - {:name "Joe's Modern Coffee House", :categories ["Modern" "Coffee House"], :phone "415-331-5269", :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"} - {:service "foursquare", :foursquare-photo-id "7d0faa6d-0d94-4920-894a-8da7537839a7", :mayor "bob"}] - ["Polk St. Mexican Coffee House is a popular and historical place to catch a bite to eat with your pet toucan." - {:small "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/small.jpg", - :medium "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/med.jpg", - :large "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "flare", :username "biggie"}] - ["Marina No-MSG Sushi is a overrated and overrated place to pitch an investor Friday nights." - {:small "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/small.jpg", - :medium "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/med.jpg", - :large "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/large.jpg"} - {:name "Marina No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-856-5937", :id "d51013a3-8547-4705-a5f0-cb11d8206481"} - {:service "twitter", :mentions ["@marina_no_msg_sushi"], :tags ["#no-msg" "#sushi"], :username "rasta_toucan"}] - ["Sunset Deep-Dish Hotel & Restaurant is a horrible and world-famous place to catch a bite to eat Friday nights." - {:small "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/small.jpg", - :medium "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/med.jpg", - :large "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/large.jpg"} - {:name "Sunset Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-332-0978", :id "a80745c7-af74-4579-8932-70dd488269e6"} - {:service "twitter", :mentions ["@sunset_deep_dish_hotel_&_restaurant"], :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], :username "rasta_toucan"}] - ["Pacific Heights Free-Range Eatery is a wonderful and modern place to take visiting friends and relatives Friday nights." - {:small "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/small.jpg", - :medium "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/med.jpg", - :large "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "twitter", :mentions ["@pacific_heights_free_range_eatery"], :tags ["#free-range" "#eatery"], :username "kyle"}] - ["Lucky's Deep-Dish Gastro Pub is a decent and delicious place to watch the Giants game on Taco Tuesday." - {:small "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/small.jpg", - :medium "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/med.jpg", - :large "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/large.jpg"} - {:name "Lucky's Deep-Dish Gastro Pub", :categories ["Deep-Dish" "Gastro Pub"], :phone "415-487-4085", :id "0136c454-0968-41cd-a237-ceec5724cab8"} - {:service "facebook", :facebook-photo-id "dfad685c-f2a7-4ab4-ba45-a9d94919d8f6", :url "http://facebook.com/photos/dfad685c-f2a7-4ab4-ba45-a9d94919d8f6"}] - ["Polk St. Mexican Coffee House is a world-famous and horrible place to pitch an investor the second Saturday of the month." - {:small "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/small.jpg", - :medium "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/med.jpg", - :large "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "twitter", :mentions ["@polk_st._mexican_coffee_house"], :tags ["#mexican" "#coffee" "#house"], :username "bob"}] - ["Pacific Heights Pizza Bakery is a acceptable and fantastic place to have a after-work cocktail after baseball games." - {:small "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/small.jpg", - :medium "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/med.jpg", - :large "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "yelp", :yelp-photo-id "a7da25d3-cf05-444f-9a1c-11541fdbdb78", :categories ["Pizza" "Bakery"]}] - ["SoMa Old-Fashioned Pizzeria is a underappreciated and groovy place to take a date with your pet dog." - {:small "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/small.jpg", - :medium "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/med.jpg", - :large "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "flare", :username "lucky_pigeon"}] - ["Marina Homestyle Pop-Up Food Stand is a amazing and acceptable place to take visiting friends and relatives during winter." - {:small "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/small.jpg", - :medium "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/med.jpg", - :large "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/large.jpg"} - {:name "Marina Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-094-4567", :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"} - {:service "yelp", :yelp-photo-id "a031364b-8035-45ba-8948-3e3d42cd0bb1", :categories ["Homestyle" "Pop-Up Food Stand"]}] - ["Haight European Grill is a horrible and amazing place to have a birthday party during winter." - {:small "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/small.jpg", - :medium "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/med.jpg", - :large "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/large.jpg"} - {:name "Haight European Grill", :categories ["European" "Grill"], :phone "415-191-2778", :id "7e6281f7-5b17-4056-ada0-85453247bc8f"} - {:service "twitter", :mentions ["@haight_european_grill"], :tags ["#european" "#grill"], :username "kyle"}] - ["SoMa Japanese Churros is a horrible and overrated place to people-watch during winter." - {:small "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/small.jpg", - :medium "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/med.jpg", - :large "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "facebook", :facebook-photo-id "d9c67f4f-f651-4e29-91bf-fe8a13c51a42", :url "http://facebook.com/photos/d9c67f4f-f651-4e29-91bf-fe8a13c51a42"}] - ["Marina Japanese Liquor Store is a wonderful and historical place to people-watch in July." - {:small "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/small.jpg", - :medium "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/med.jpg", - :large "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/large.jpg"} - {:name "Marina Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-587-9819", :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"} - {:service "yelp", :yelp-photo-id "a09b2b6a-2cc4-4c75-ad94-bc4b255f2609", :categories ["Japanese" "Liquor Store"]}] - ["Nob Hill Gluten-Free Coffee House is a popular and historical place to take visiting friends and relatives weekend mornings." - {:small "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/small.jpg", - :medium "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/med.jpg", - :large "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "foursquare", :foursquare-photo-id "e11de3d3-8166-424c-91a4-857d3abb8487", :mayor "kyle"}] - ["Polk St. Deep-Dish Hotel & Restaurant is a family-friendly and amazing place to take visiting friends and relatives in the spring." - {:small "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/small.jpg", - :medium "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/med.jpg", - :large "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "yelp", :yelp-photo-id "101ea39b-5bc5-4bb7-905e-017f1a8271c8", :categories ["Deep-Dish" "Hotel & Restaurant"]}] - ["Joe's No-MSG Sushi is a underappreciated and family-friendly place to people-watch weekend evenings." - {:small "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/small.jpg", - :medium "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/med.jpg", - :large "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/large.jpg"} - {:name "Joe's No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-739-8157", :id "9ff21570-cd5b-415e-933a-52144f551b86"} - {:service "flare", :username "sameer"}] - ["Mission Soul Food Pizzeria is a acceptable and historical place to pitch an investor during winter." - {:small "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/small.jpg", - :medium "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/med.jpg", - :large "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/large.jpg"} - {:name "Mission Soul Food Pizzeria", :categories ["Soul Food" "Pizzeria"], :phone "415-437-3479", :id "9905fe61-44cb-4626-843b-5d725c7949bb"} - {:service "yelp", :yelp-photo-id "22c576f1-d64a-44c1-a510-85ed03cd8a9c", :categories ["Soul Food" "Pizzeria"]}] - ["Joe's Homestyle Eatery is a popular and underappreciated place to drink a craft beer with your pet toucan." - {:small "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/small.jpg", - :medium "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/med.jpg", - :large "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "facebook", :facebook-photo-id "3e5b3749-a758-4acf-b87e-aadca225127c", :url "http://facebook.com/photos/3e5b3749-a758-4acf-b87e-aadca225127c"}] - ["Market St. Gluten-Free Café is a family-friendly and family-friendly place to have a after-work cocktail when hungover." - {:small "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/small.jpg", - :medium "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/med.jpg", - :large "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "flare", :username "amy"}] - ["Tenderloin Cage-Free Sushi is a swell and classic place to nurse a hangover with your pet toucan." - {:small "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/small.jpg", - :medium "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/med.jpg", - :large "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "twitter", :mentions ["@tenderloin_cage_free_sushi"], :tags ["#cage-free" "#sushi"], :username "joe"}] - ["Mission Chinese Liquor Store is a family-friendly and great place to take visiting friends and relatives weekday afternoons." - {:small "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/small.jpg", - :medium "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/med.jpg", - :large "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/large.jpg"} - {:name "Mission Chinese Liquor Store", :categories ["Chinese" "Liquor Store"], :phone "415-906-6919", :id "00132b5b-31fc-46f0-a288-f547f23477ee"} - {:service "twitter", :mentions ["@mission_chinese_liquor_store"], :tags ["#chinese" "#liquor" "#store"], :username "jane"}] - ["Lucky's Low-Carb Coffee House is a great and decent place to conduct a business meeting when hungover." - {:small "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/small.jpg", - :medium "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/med.jpg", - :large "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/large.jpg"} - {:name "Lucky's Low-Carb Coffee House", :categories ["Low-Carb" "Coffee House"], :phone "415-145-7107", :id "81b0f944-f0ce-45e5-b84e-a924c441064a"} - {:service "foursquare", :foursquare-photo-id "f7b66a97-8c94-4754-8938-6b4e3244b08e", :mayor "jane"}] - ["Lower Pac Heights Cage-Free Coffee House is a exclusive and fantastic place to take visiting friends and relatives with your pet dog." - {:small "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/small.jpg", - :medium "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/med.jpg", - :large "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "flare", :username "jane"}] - ["SoMa TaquerÃa Diner is a overrated and amazing place to have breakfast in June." - {:small "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/small.jpg", - :medium "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/med.jpg", - :large "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/large.jpg"} - {:name "SoMa TaquerÃa Diner", :categories ["TaquerÃa" "Diner"], :phone "415-947-9521", :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"} - {:service "yelp", :yelp-photo-id "cf483e10-dd11-4611-90e0-bc0238b41b59", :categories ["TaquerÃa" "Diner"]}] - ["Cam's Mexican Gastro Pub is a swell and world-famous place to take visiting friends and relatives Friday nights." - {:small "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/small.jpg", - :medium "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/med.jpg", - :large "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "yelp", :yelp-photo-id "33a70c42-6c5a-4a29-af94-b7856cbc6cbb", :categories ["Mexican" "Gastro Pub"]}] - ["Alcatraz Pizza Churros is a horrible and underground place to sip a glass of expensive wine during winter." - {:small "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/small.jpg", - :medium "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/med.jpg", - :large "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/large.jpg"} - {:name "Alcatraz Pizza Churros", :categories ["Pizza" "Churros"], :phone "415-754-7867", :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"} - {:service "yelp", :yelp-photo-id "e6a2e47f-07b6-4ec9-a491-371fb1959975", :categories ["Pizza" "Churros"]}] - ["Marina Low-Carb Food Truck is a groovy and delicious place to watch the Giants game on Thursdays." - {:small "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/small.jpg", - :medium "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/med.jpg", - :large "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "flare", :username "tupac"}] - ["Rasta's Paleo Churros is a acceptable and family-friendly place to have brunch weekday afternoons." - {:small "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/small.jpg", - :medium "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/med.jpg", - :large "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "facebook", :facebook-photo-id "0b3c791c-935c-4d01-84f8-9708c699eb1b", :url "http://facebook.com/photos/0b3c791c-935c-4d01-84f8-9708c699eb1b"}] - ["Tenderloin Red White & Blue Pizzeria is a swell and historical place to nurse a hangover Friday nights." - {:small "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/small.jpg", - :medium "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/med.jpg", - :large "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/large.jpg"} - {:name "Tenderloin Red White & Blue Pizzeria", :categories ["Red White & Blue" "Pizzeria"], :phone "415-719-8143", :id "eba3dbcd-100a-4f38-a701-e0dec157f437"} - {:service "yelp", :yelp-photo-id "c6aa321a-e8f7-484b-9fd1-ab8f6a1c5906", :categories ["Red White & Blue" "Pizzeria"]}] - ["Kyle's Free-Range Taqueria is a amazing and wonderful place to have breakfast in the fall." - {:small "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/small.jpg", - :medium "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/med.jpg", - :large "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/large.jpg"} - {:name "Kyle's Free-Range Taqueria", :categories ["Free-Range" "Taqueria"], :phone "415-201-7832", :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"} - {:service "flare", :username "amy"}] - ["Lucky's Cage-Free Liquor Store is a delicious and acceptable place to drink a craft beer on public holidays." - {:small "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/small.jpg", - :medium "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/med.jpg", - :large "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/large.jpg"} - {:name "Lucky's Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-341-3219", :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"} - {:service "foursquare", :foursquare-photo-id "ef083c69-08d6-45be-8cfb-c71654b0a8fc", :mayor "cam_saul"}] - ["Lucky's Gluten-Free Café is a groovy and wonderful place to have a drink with friends." - {:small "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/small.jpg", - :medium "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/med.jpg", - :large "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/large.jpg"} - {:name "Lucky's Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-740-2328", :id "379af987-ad40-4a93-88a6-0233e1c14649"} - {:service "twitter", :mentions ["@luckys_gluten_free_café"], :tags ["#gluten-free" "#café"], :username "biggie"}] - ["Marina Modern Sushi is a wonderful and exclusive place to watch the Warriors game the second Saturday of the month." - {:small "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/small.jpg", - :medium "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/med.jpg", - :large "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/large.jpg"} - {:name "Marina Modern Sushi", :categories ["Modern" "Sushi"], :phone "415-393-7672", :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"} - {:service "flare", :username "kyle"}] - ["Pacific Heights Red White & Blue Bar & Grill is a decent and delicious place to watch the Giants game when hungover." - {:small "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/small.jpg", - :medium "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/med.jpg", - :large "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "foursquare", :foursquare-photo-id "55c948c7-8d08-46e1-98e6-a03ca330795c", :mayor "mandy"}] - ["Joe's Modern Coffee House is a acceptable and exclusive place to catch a bite to eat during summer." - {:small "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/small.jpg", - :medium "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/med.jpg", - :large "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/large.jpg"} - {:name "Joe's Modern Coffee House", :categories ["Modern" "Coffee House"], :phone "415-331-5269", :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"} - {:service "flare", :username "kyle"}] - ["Haight Gormet Pizzeria is a historical and modern place to pitch an investor Friday nights." - {:small "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/small.jpg", - :medium "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/med.jpg", - :large "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/large.jpg"} - {:name "Haight Gormet Pizzeria", :categories ["Gormet" "Pizzeria"], :phone "415-869-2197", :id "0425bdd0-3f57-4108-80e3-78335327355a"} - {:service "flare", :username "cam_saul"}] - ["Cam's Mexican Gastro Pub is a world-famous and fantastic place to take a date in July." - {:small "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/small.jpg", - :medium "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/med.jpg", - :large "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "yelp", :yelp-photo-id "c76eeafe-a161-492d-8a84-f0d3eb729d2b", :categories ["Mexican" "Gastro Pub"]}] - ["Haight European Grill is a acceptable and underground place to have breakfast on a Tuesday afternoon." - {:small "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/small.jpg", - :medium "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/med.jpg", - :large "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/large.jpg"} - {:name "Haight European Grill", :categories ["European" "Grill"], :phone "415-191-2778", :id "7e6281f7-5b17-4056-ada0-85453247bc8f"} - {:service "flare", :username "joe"}] - ["Tenderloin Cage-Free Sushi is a modern and atmospheric place to sip Champagne weekday afternoons." - {:small "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/small.jpg", - :medium "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/med.jpg", - :large "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "facebook", :facebook-photo-id "fd4144e1-bddb-4f4d-8985-de94a554a730", :url "http://facebook.com/photos/fd4144e1-bddb-4f4d-8985-de94a554a730"}] - ["Lower Pac Heights Deep-Dish Liquor Store is a horrible and decent place to pitch an investor on Taco Tuesday." - {:small "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/small.jpg", - :medium "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/med.jpg", - :large "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Liquor Store", :categories ["Deep-Dish" "Liquor Store"], :phone "415-497-3039", :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"} - {:service "yelp", :yelp-photo-id "50bdc257-fcc9-44d1-b121-87f5c3e45058", :categories ["Deep-Dish" "Liquor Store"]}] - ["Mission Free-Range Liquor Store is a popular and fantastic place to watch the Giants game on Taco Tuesday." - {:small "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/small.jpg", - :medium "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/med.jpg", - :large "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/large.jpg"} - {:name "Mission Free-Range Liquor Store", :categories ["Free-Range" "Liquor Store"], :phone "415-041-3816", :id "6e665924-8e2c-42ab-af58-23a27f017e37"} - {:service "foursquare", :foursquare-photo-id "90c4812a-a426-4776-aeb1-81e4770c7887", :mayor "sameer"}] - ["SoMa British Bakery is a wonderful and historical place to have a drink during winter." - {:small "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/small.jpg", - :medium "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/med.jpg", - :large "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/large.jpg"} - {:name "SoMa British Bakery", :categories ["British" "Bakery"], :phone "415-909-5728", :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"} - {:service "yelp", :yelp-photo-id "d2643a6d-6669-48e5-890e-080aaff6d1e7", :categories ["British" "Bakery"]}] - ["Market St. Gluten-Free Café is a great and modern place to sip a glass of expensive wine Friday nights." - {:small "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/small.jpg", - :medium "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/med.jpg", - :large "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "foursquare", :foursquare-photo-id "7ae76e90-44db-483e-a5bc-2cb8ec65fa61", :mayor "jessica"}] - ["Marina No-MSG Sushi is a fantastic and classic place to have brunch with your pet dog." - {:small "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/small.jpg", - :medium "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/med.jpg", - :large "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/large.jpg"} - {:name "Marina No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-856-5937", :id "d51013a3-8547-4705-a5f0-cb11d8206481"} - {:service "twitter", :mentions ["@marina_no_msg_sushi"], :tags ["#no-msg" "#sushi"], :username "jane"}] - ["Sunset American Churros is a underappreciated and world-famous place to have brunch with friends." - {:small "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/small.jpg", - :medium "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/med.jpg", - :large "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "foursquare", :foursquare-photo-id "1b34eb04-290c-409e-b63c-ee82fff83878", :mayor "mandy"}] - ["Sameer's Pizza Liquor Store is a decent and underground place to meet new friends the second Saturday of the month." - {:small "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/small.jpg", - :medium "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/med.jpg", - :large "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "flare", :username "biggie"}] - ["SF Deep-Dish Eatery is a delicious and modern place to watch the Warriors game on Saturday night." - {:small "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/small.jpg", - :medium "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/med.jpg", - :large "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/large.jpg"} - {:name "SF Deep-Dish Eatery", :categories ["Deep-Dish" "Eatery"], :phone "415-476-9257", :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"} - {:service "yelp", :yelp-photo-id "79616795-0585-478a-9998-8c9b0169005e", :categories ["Deep-Dish" "Eatery"]}] - ["SF Afgan Restaurant is a fantastic and exclusive place to nurse a hangover in June." - {:small "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/small.jpg", - :medium "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/med.jpg", - :large "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/large.jpg"} - {:name "SF Afgan Restaurant", :categories ["Afgan" "Restaurant"], :phone "415-451-4697", :id "66ccc68a-db9a-470c-a17b-7764d23daced"} - {:service "foursquare", :foursquare-photo-id "c00e6384-fc26-4056-86fc-95df69092552", :mayor "kyle"}] - ["Pacific Heights Red White & Blue Bar & Grill is a swell and atmospheric place to conduct a business meeting the second Saturday of the month." - {:small "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/small.jpg", - :medium "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/med.jpg", - :large "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "facebook", :facebook-photo-id "d4c43b07-932f-42d3-b70f-29306c9f0746", :url "http://facebook.com/photos/d4c43b07-932f-42d3-b70f-29306c9f0746"}] - ["Lower Pac Heights Cage-Free Coffee House is a acceptable and family-friendly place to nurse a hangover during winter." - {:small "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/small.jpg", - :medium "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/med.jpg", - :large "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "flare", :username "rasta_toucan"}] - ["Oakland American Grill is a amazing and swell place to catch a bite to eat the first Sunday of the month." - {:small "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/small.jpg", - :medium "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/med.jpg", - :large "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "yelp", :yelp-photo-id "38bc4d27-d28f-434b-b89d-5b88384a1c9b", :categories ["American" "Grill"]}] - ["Joe's No-MSG Sushi is a well-decorated and delicious place to catch a bite to eat on Thursdays." - {:small "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/small.jpg", - :medium "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/med.jpg", - :large "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/large.jpg"} - {:name "Joe's No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-739-8157", :id "9ff21570-cd5b-415e-933a-52144f551b86"} - {:service "twitter", :mentions ["@joes_no_msg_sushi"], :tags ["#no-msg" "#sushi"], :username "joe"}] - ["Rasta's British Food Truck is a historical and well-decorated place to take visiting friends and relatives on a Tuesday afternoon." - {:small "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/small.jpg", - :medium "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/med.jpg", - :large "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/large.jpg"} - {:name "Rasta's British Food Truck", :categories ["British" "Food Truck"], :phone "415-958-9031", :id "b6616c97-01d0-488f-a855-bcd6efe2b899"} - {:service "flare", :username "jessica"}] - ["Polk St. Deep-Dish Hotel & Restaurant is a groovy and horrible place to take visiting friends and relatives with your pet toucan." - {:small "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/small.jpg", - :medium "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/med.jpg", - :large "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "flare", :username "tupac"}] - ["Market St. Gluten-Free Café is a delicious and delicious place to catch a bite to eat on a Tuesday afternoon." - {:small "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/small.jpg", - :medium "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/med.jpg", - :large "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "flare", :username "mandy"}] - ["Marina Modern Sushi is a underground and family-friendly place to sip Champagne Friday nights." - {:small "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/small.jpg", - :medium "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/med.jpg", - :large "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/large.jpg"} - {:name "Marina Modern Sushi", :categories ["Modern" "Sushi"], :phone "415-393-7672", :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"} - {:service "flare", :username "joe"}] - ["Pacific Heights Pizza Bakery is a underappreciated and popular place to sip a glass of expensive wine the first Sunday of the month." - {:small "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/small.jpg", - :medium "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/med.jpg", - :large "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "yelp", :yelp-photo-id "8e817a6f-5d3d-4fff-9c80-b5ed3e225095", :categories ["Pizza" "Bakery"]}] - ["Kyle's Japanese Hotel & Restaurant is a amazing and family-friendly place to have breakfast in June." - {:small "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/small.jpg", - :medium "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/med.jpg", - :large "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/large.jpg"} - {:name "Kyle's Japanese Hotel & Restaurant", :categories ["Japanese" "Hotel & Restaurant"], :phone "415-337-5387", :id "eced4f41-b627-4553-a297-888871038b69"} - {:service "flare", :username "mandy"}] - ["Pacific Heights No-MSG Sushi is a groovy and groovy place to drink a craft beer when hungover." - {:small "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/small.jpg", - :medium "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/med.jpg", - :large "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/large.jpg"} - {:name "Pacific Heights No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-354-9547", :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"} - {:service "facebook", :facebook-photo-id "265681c6-d000-4618-87fc-f8e35eef9ee3", :url "http://facebook.com/photos/265681c6-d000-4618-87fc-f8e35eef9ee3"}] - ["Joe's Homestyle Eatery is a decent and fantastic place to conduct a business meeting on public holidays." - {:small "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/small.jpg", - :medium "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/med.jpg", - :large "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "yelp", :yelp-photo-id "69b17ca8-3a3e-4df2-a46b-a18d4d82283d", :categories ["Homestyle" "Eatery"]}] - ["Pacific Heights Irish Grill is a world-famous and delicious place to conduct a business meeting with friends." - {:small "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/small.jpg", - :medium "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/med.jpg", - :large "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/large.jpg"} - {:name "Pacific Heights Irish Grill", :categories ["Irish" "Grill"], :phone "415-491-2202", :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"} - {:service "flare", :username "jane"}] - ["Marina Japanese Liquor Store is a classic and groovy place to watch the Giants game Friday nights." - {:small "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/small.jpg", - :medium "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/med.jpg", - :large "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/large.jpg"} - {:name "Marina Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-587-9819", :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"} - {:service "twitter", :mentions ["@marina_japanese_liquor_store"], :tags ["#japanese" "#liquor" "#store"], :username "bob"}] - ["Pacific Heights Soul Food Coffee House is a wonderful and underappreciated place to watch the Warriors game in July." - {:small "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/small.jpg", - :medium "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/med.jpg", - :large "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/large.jpg"} - {:name "Pacific Heights Soul Food Coffee House", :categories ["Soul Food" "Coffee House"], :phone "415-838-3464", :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"} - {:service "flare", :username "amy"}] - ["Polk St. Korean Taqueria is a amazing and historical place to people-watch on Saturday night." - {:small "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/small.jpg", - :medium "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/med.jpg", - :large "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "twitter", :mentions ["@polk_st._korean_taqueria"], :tags ["#korean" "#taqueria"], :username "bob"}] - ["Lucky's Cage-Free Liquor Store is a delicious and amazing place to catch a bite to eat on a Tuesday afternoon." - {:small "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/small.jpg", - :medium "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/med.jpg", - :large "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/large.jpg"} - {:name "Lucky's Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-341-3219", :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"} - {:service "facebook", :facebook-photo-id "8ce61a42-cb9c-4304-bada-3a8fb245bb64", :url "http://facebook.com/photos/8ce61a42-cb9c-4304-bada-3a8fb245bb64"}] - ["SoMa Japanese Churros is a wonderful and modern place to conduct a business meeting during winter." - {:small "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/small.jpg", - :medium "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/med.jpg", - :large "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "facebook", :facebook-photo-id "4e3d4ad7-54ba-4f55-baf7-4ad223bcfaf6", :url "http://facebook.com/photos/4e3d4ad7-54ba-4f55-baf7-4ad223bcfaf6"}] - ["SoMa Old-Fashioned Pizzeria is a well-decorated and horrible place to meet new friends in the fall." - {:small "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/small.jpg", - :medium "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/med.jpg", - :large "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "facebook", :facebook-photo-id "91e06ea0-df70-48e2-bb43-6acfe787d203", :url "http://facebook.com/photos/91e06ea0-df70-48e2-bb43-6acfe787d203"}] - ["Oakland European Liquor Store is a popular and historical place to sip Champagne with your pet toucan." - {:small "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/small.jpg", - :medium "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/med.jpg", - :large "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/large.jpg"} - {:name "Oakland European Liquor Store", :categories ["European" "Liquor Store"], :phone "415-559-1516", :id "e342e7b7-e82d-475d-a822-b2df9c84850d"} - {:service "flare", :username "cam_saul"}] - ["Pacific Heights Pizza Bakery is a classic and classic place to have a birthday party on public holidays." - {:small "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/small.jpg", - :medium "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/med.jpg", - :large "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "twitter", :mentions ["@pacific_heights_pizza_bakery"], :tags ["#pizza" "#bakery"], :username "mandy"}] - ["Sameer's GMO-Free Restaurant is a decent and family-friendly place to take visiting friends and relatives during winter." - {:small "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/small.jpg", - :medium "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/med.jpg", - :large "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/large.jpg"} - {:name "Sameer's GMO-Free Restaurant", :categories ["GMO-Free" "Restaurant"], :phone "415-128-9430", :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"} - {:service "flare", :username "tupac"}] - ["Pacific Heights Pizza Bakery is a great and swell place to take a date on Thursdays." - {:small "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/small.jpg", - :medium "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/med.jpg", - :large "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "facebook", :facebook-photo-id "f420e2a8-13ce-4753-b30e-493d6e59f7ce", :url "http://facebook.com/photos/f420e2a8-13ce-4753-b30e-493d6e59f7ce"}] - ["Lucky's Gluten-Free Gastro Pub is a exclusive and overrated place to nurse a hangover in June." - {:small "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/small.jpg", - :medium "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/med.jpg", - :large "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "yelp", :yelp-photo-id "22fac240-8066-4a4d-a3a4-08c268bca17d", :categories ["Gluten-Free" "Gastro Pub"]}] - ["Oakland Low-Carb Bakery is a classic and modern place to have brunch weekday afternoons." - {:small "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/small.jpg", - :medium "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/med.jpg", - :large "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/large.jpg"} - {:name "Oakland Low-Carb Bakery", :categories ["Low-Carb" "Bakery"], :phone "415-546-0101", :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"} - {:service "flare", :username "jane"}] - ["Tenderloin Gormet Restaurant is a underground and classic place to have breakfast weekend mornings." - {:small "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/small.jpg", - :medium "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/med.jpg", - :large "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/large.jpg"} - {:name "Tenderloin Gormet Restaurant", :categories ["Gormet" "Restaurant"], :phone "415-127-4197", :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"} - {:service "flare", :username "joe"}] - ["SoMa TaquerÃa Diner is a historical and world-famous place to watch the Warriors game on Saturday night." - {:small "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/small.jpg", - :medium "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/med.jpg", - :large "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/large.jpg"} - {:name "SoMa TaquerÃa Diner", :categories ["TaquerÃa" "Diner"], :phone "415-947-9521", :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"} - {:service "flare", :username "lucky_pigeon"}] - ["Kyle's Low-Carb Grill is a world-famous and amazing place to nurse a hangover on Thursdays." - {:small "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/small.jpg", - :medium "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/med.jpg", - :large "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "facebook", :facebook-photo-id "9edda8de-49c1-41f7-8c22-06c0fc7cfc10", :url "http://facebook.com/photos/9edda8de-49c1-41f7-8c22-06c0fc7cfc10"}] - ["Haight Soul Food Pop-Up Food Stand is a modern and acceptable place to watch the Warriors game in the spring." - {:small "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/small.jpg", - :medium "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/med.jpg", - :large "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "facebook", :facebook-photo-id "234f4d52-f5b6-4214-9fbc-13ae88e25cdb", :url "http://facebook.com/photos/234f4d52-f5b6-4214-9fbc-13ae88e25cdb"}] - ["Rasta's Paleo Churros is a fantastic and classic place to nurse a hangover during summer." - {:small "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/small.jpg", - :medium "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/med.jpg", - :large "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "foursquare", :foursquare-photo-id "ced4f00d-21ed-4b10-90f6-8d5db5cc24e7", :mayor "bob"}] - ["Rasta's Paleo Churros is a fantastic and delicious place to take a date when hungover." - {:small "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/small.jpg", - :medium "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/med.jpg", - :large "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "flare", :username "mandy"}] - ["SF Afgan Restaurant is a groovy and family-friendly place to have a after-work cocktail weekend mornings." - {:small "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/small.jpg", - :medium "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/med.jpg", - :large "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/large.jpg"} - {:name "SF Afgan Restaurant", :categories ["Afgan" "Restaurant"], :phone "415-451-4697", :id "66ccc68a-db9a-470c-a17b-7764d23daced"} - {:service "flare", :username "biggie"}] - ["Mission Homestyle Churros is a historical and well-decorated place to catch a bite to eat weekend evenings." - {:small "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/small.jpg", - :medium "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/med.jpg", - :large "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "twitter", :mentions ["@mission_homestyle_churros"], :tags ["#homestyle" "#churros"], :username "biggie"}] - ["Pacific Heights Free-Range Eatery is a groovy and overrated place to sip a glass of expensive wine in the spring." - {:small "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/small.jpg", - :medium "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/med.jpg", - :large "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "yelp", :yelp-photo-id "8ebf793a-f6e8-4113-8d3b-769d6eb1a0bf", :categories ["Free-Range" "Eatery"]}] - ["Marina Cage-Free Liquor Store is a delicious and fantastic place to nurse a hangover during summer." - {:small "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/small.jpg", - :medium "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/med.jpg", - :large "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "foursquare", :foursquare-photo-id "1a1168ce-aa45-485f-a427-309eeb536510", :mayor "cam_saul"}] - ["Tenderloin Red White & Blue Pizzeria is a atmospheric and atmospheric place to nurse a hangover during winter." - {:small "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/small.jpg", - :medium "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/med.jpg", - :large "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/large.jpg"} - {:name "Tenderloin Red White & Blue Pizzeria", :categories ["Red White & Blue" "Pizzeria"], :phone "415-719-8143", :id "eba3dbcd-100a-4f38-a701-e0dec157f437"} - {:service "yelp", :yelp-photo-id "989113fd-f95f-4e62-ac21-a428e2b72334", :categories ["Red White & Blue" "Pizzeria"]}] - ["Polk St. Mexican Coffee House is a classic and swell place to watch the Giants game weekend mornings." - {:small "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/small.jpg", - :medium "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/med.jpg", - :large "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "facebook", :facebook-photo-id "4384296d-a41c-44c8-bff9-a5d64bb01f44", :url "http://facebook.com/photos/4384296d-a41c-44c8-bff9-a5d64bb01f44"}] - ["Mission Chinese Liquor Store is a horrible and atmospheric place to have a birthday party when hungover." - {:small "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/small.jpg", - :medium "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/med.jpg", - :large "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/large.jpg"} - {:name "Mission Chinese Liquor Store", :categories ["Chinese" "Liquor Store"], :phone "415-906-6919", :id "00132b5b-31fc-46f0-a288-f547f23477ee"} - {:service "yelp", :yelp-photo-id "4623b716-fb61-4349-8f79-cef3706d6b0d", :categories ["Chinese" "Liquor Store"]}] - ["Tenderloin Gormet Restaurant is a overrated and swell place to nurse a hangover in June." - {:small "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/small.jpg", - :medium "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/med.jpg", - :large "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/large.jpg"} - {:name "Tenderloin Gormet Restaurant", :categories ["Gormet" "Restaurant"], :phone "415-127-4197", :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"} - {:service "yelp", :yelp-photo-id "a4e6c1c8-afaf-48ed-b18d-755f9c6dd4b6", :categories ["Gormet" "Restaurant"]}] - ["Tenderloin Paleo Hotel & Restaurant is a modern and groovy place to pitch an investor the second Saturday of the month." - {:small "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/small.jpg", - :medium "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/med.jpg", - :large "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/large.jpg"} - {:name "Tenderloin Paleo Hotel & Restaurant", :categories ["Paleo" "Hotel & Restaurant"], :phone "415-402-1652", :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"} - {:service "flare", :username "biggie"}] - ["Pacific Heights Free-Range Eatery is a underground and family-friendly place to have a after-work cocktail with friends." - {:small "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/small.jpg", - :medium "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/med.jpg", - :large "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "yelp", :yelp-photo-id "cc9465c4-b3af-45f1-aa0d-8b38c5a6a366", :categories ["Free-Range" "Eatery"]}] - ["Lower Pac Heights Deep-Dish Ice Cream Truck is a classic and atmospheric place to meet new friends weekend mornings." - {:small "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/small.jpg", - :medium "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/med.jpg", - :large "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", :categories ["Deep-Dish" "Ice Cream Truck"], :phone "415-495-1414", :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"} - {:service "yelp", :yelp-photo-id "6b5975f9-77f6-4375-be86-5fd047a5493a", :categories ["Deep-Dish" "Ice Cream Truck"]}] - ["Mission BBQ Churros is a family-friendly and delicious place to people-watch on Saturday night." - {:small "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/small.jpg", - :medium "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/med.jpg", - :large "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/large.jpg"} - {:name "Mission BBQ Churros", :categories ["BBQ" "Churros"], :phone "415-406-5374", :id "429ea81a-02c5-449f-bfa7-03a11b227f1f"} - {:service "yelp", :yelp-photo-id "dd02f753-593e-4bc5-b370-08a6fe46de96", :categories ["BBQ" "Churros"]}] - ["Pacific Heights Soul Food Coffee House is a family-friendly and wonderful place to pitch an investor weekday afternoons." - {:small "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/small.jpg", - :medium "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/med.jpg", - :large "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/large.jpg"} - {:name "Pacific Heights Soul Food Coffee House", :categories ["Soul Food" "Coffee House"], :phone "415-838-3464", :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"} - {:service "twitter", :mentions ["@pacific_heights_soul_food_coffee_house"], :tags ["#soul" "#food" "#coffee" "#house"], :username "mandy"}] - ["Mission Soul Food Pizzeria is a groovy and underground place to take visiting friends and relatives in July." - {:small "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/small.jpg", - :medium "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/med.jpg", - :large "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/large.jpg"} - {:name "Mission Soul Food Pizzeria", :categories ["Soul Food" "Pizzeria"], :phone "415-437-3479", :id "9905fe61-44cb-4626-843b-5d725c7949bb"} - {:service "yelp", :yelp-photo-id "bf759d74-af3f-4a57-addb-1349f2b2f8c9", :categories ["Soul Food" "Pizzeria"]}] - ["Chinatown American Bakery is a decent and great place to have brunch during winter." - {:small "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/small.jpg", - :medium "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/med.jpg", - :large "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/large.jpg"} - {:name "Chinatown American Bakery", :categories ["American" "Bakery"], :phone "415-658-7393", :id "cf55cdbd-c614-4be1-8496-0e11b195d16f"} - {:service "flare", :username "rasta_toucan"}] - ["Cam's Old-Fashioned Coffee House is a groovy and popular place to have a birthday party in the fall." - {:small "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/small.jpg", - :medium "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/med.jpg", - :large "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-868-2973", :id "27592c2b-e682-44bb-be28-8e9a622becca"} - {:service "twitter", :mentions ["@cams_old_fashioned_coffee_house"], :tags ["#old-fashioned" "#coffee" "#house"], :username "lucky_pigeon"}] - ["Pacific Heights No-MSG Sushi is a well-decorated and amazing place to conduct a business meeting in the fall." - {:small "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/small.jpg", - :medium "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/med.jpg", - :large "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/large.jpg"} - {:name "Pacific Heights No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-354-9547", :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"} - {:service "facebook", :facebook-photo-id "251a7bf8-73d3-44b8-9f55-2ee0048158d1", :url "http://facebook.com/photos/251a7bf8-73d3-44b8-9f55-2ee0048158d1"}] - ["Mission Chinese Liquor Store is a underappreciated and acceptable place to have breakfast with friends." - {:small "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/small.jpg", - :medium "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/med.jpg", - :large "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/large.jpg"} - {:name "Mission Chinese Liquor Store", :categories ["Chinese" "Liquor Store"], :phone "415-906-6919", :id "00132b5b-31fc-46f0-a288-f547f23477ee"} - {:service "facebook", :facebook-photo-id "b2fa77dd-6e6d-440e-8f12-caa3ea7c120c", :url "http://facebook.com/photos/b2fa77dd-6e6d-440e-8f12-caa3ea7c120c"}] - ["Kyle's Old-Fashioned Pop-Up Food Stand is a underappreciated and groovy place to catch a bite to eat on a Tuesday afternoon." - {:small "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/small.jpg", - :medium "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/med.jpg", - :large "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/large.jpg"} - {:name "Kyle's Old-Fashioned Pop-Up Food Stand", :categories ["Old-Fashioned" "Pop-Up Food Stand"], :phone "415-638-8972", :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"} - {:service "flare", :username "sameer"}] - ["Sameer's GMO-Free Restaurant is a groovy and modern place to people-watch after baseball games." - {:small "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/small.jpg", - :medium "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/med.jpg", - :large "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/large.jpg"} - {:name "Sameer's GMO-Free Restaurant", :categories ["GMO-Free" "Restaurant"], :phone "415-128-9430", :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"} - {:service "twitter", :mentions ["@sameers_gmo_free_restaurant"], :tags ["#gmo-free" "#restaurant"], :username "tupac"}] - ["Haight Soul Food Pop-Up Food Stand is a underground and modern place to have breakfast on a Tuesday afternoon." - {:small "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/small.jpg", - :medium "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/med.jpg", - :large "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "twitter", :mentions ["@haight_soul_food_pop_up_food_stand"], :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], :username "kyle"}] - ["Pacific Heights Irish Grill is a acceptable and world-famous place to have a after-work cocktail on Saturday night." - {:small "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/small.jpg", - :medium "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/med.jpg", - :large "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/large.jpg"} - {:name "Pacific Heights Irish Grill", :categories ["Irish" "Grill"], :phone "415-491-2202", :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"} - {:service "flare", :username "mandy"}] - ["Mission Homestyle Churros is a decent and horrible place to pitch an investor on Taco Tuesday." - {:small "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/small.jpg", - :medium "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/med.jpg", - :large "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/large.jpg"} - {:name "Mission Homestyle Churros", :categories ["Homestyle" "Churros"], :phone "415-343-4489", :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"} - {:service "flare", :username "sameer"}] - ["Haight Soul Food Sushi is a well-decorated and wonderful place to drink a craft beer with friends." - {:small "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/small.jpg", - :medium "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/med.jpg", - :large "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/large.jpg"} - {:name "Haight Soul Food Sushi", :categories ["Soul Food" "Sushi"], :phone "415-371-8026", :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"} - {:service "foursquare", :foursquare-photo-id "95609cc6-1197-4713-bc72-7e3e799dbb6c", :mayor "jessica"}] - ["Market St. Homestyle Pop-Up Food Stand is a historical and great place to nurse a hangover weekday afternoons." - {:small "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/small.jpg", - :medium "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/med.jpg", - :large "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/large.jpg"} - {:name "Market St. Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-213-3030", :id "2d873280-e43d-449e-9940-af96ae7df718"} - {:service "foursquare", :foursquare-photo-id "002d03c2-9c91-4160-a9e8-4603ae977245", :mayor "sameer"}] - ["Sameer's European Sushi is a atmospheric and horrible place to take a date on Taco Tuesday." - {:small "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/small.jpg", - :medium "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/med.jpg", - :large "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/large.jpg"} - {:name "Sameer's European Sushi", :categories ["European" "Sushi"], :phone "415-035-2474", :id "7de6b3ef-7b53-4831-bf76-43123874f8ce"} - {:service "yelp", :yelp-photo-id "6ccfda4c-84d2-4108-8cb1-699d28cd1574", :categories ["European" "Sushi"]}] - ["Polk St. Korean Taqueria is a family-friendly and horrible place to take a date in the spring." - {:small "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/small.jpg", - :medium "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/med.jpg", - :large "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "flare", :username "bob"}] - ["Polk St. Red White & Blue Café is a popular and swell place to have a birthday party in June." - {:small "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/small.jpg", - :medium "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/med.jpg", - :large "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "facebook", :facebook-photo-id "276865a5-e7d3-40f6-a2b2-62172caf3024", :url "http://facebook.com/photos/276865a5-e7d3-40f6-a2b2-62172caf3024"}] - ["Lucky's Gluten-Free Gastro Pub is a underappreciated and historical place to watch the Warriors game with friends." - {:small "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/small.jpg", - :medium "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/med.jpg", - :large "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "flare", :username "kyle"}] - ["Rasta's Paleo Café is a decent and swell place to have a drink weekend evenings." - {:small "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/small.jpg", - :medium "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/med.jpg", - :large "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/large.jpg"} - {:name "Rasta's Paleo Café", :categories ["Paleo" "Café"], :phone "415-392-6341", :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"} - {:service "facebook", :facebook-photo-id "26cff009-fce9-4439-a263-39df23a3b143", :url "http://facebook.com/photos/26cff009-fce9-4439-a263-39df23a3b143"}] - ["Polk St. Mexican Coffee House is a modern and historical place to have brunch when hungover." - {:small "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/small.jpg", - :medium "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/med.jpg", - :large "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "yelp", :yelp-photo-id "b82466db-dddb-451c-ba09-47d520df8730", :categories ["Mexican" "Coffee House"]}] - ["Polk St. Korean Taqueria is a amazing and classic place to take visiting friends and relatives weekend evenings." - {:small "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/small.jpg", - :medium "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/med.jpg", - :large "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "foursquare", :foursquare-photo-id "686a4c8a-51ca-4f6b-b216-d4b53dc11944", :mayor "rasta_toucan"}] - ["Pacific Heights Free-Range Eatery is a groovy and swell place to sip a glass of expensive wine during summer." - {:small "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/small.jpg", - :medium "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/med.jpg", - :large "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "flare", :username "sameer"}] - ["Market St. Low-Carb Taqueria is a delicious and groovy place to watch the Warriors game with friends." - {:small "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/small.jpg", - :medium "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/med.jpg", - :large "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/large.jpg"} - {:name "Market St. Low-Carb Taqueria", :categories ["Low-Carb" "Taqueria"], :phone "415-751-6525", :id "f30eb85b-f048-4d8c-8008-3c2876125061"} - {:service "facebook", :facebook-photo-id "b136f58c-6b1f-433b-bce8-c803040ff9bd", :url "http://facebook.com/photos/b136f58c-6b1f-433b-bce8-c803040ff9bd"}] - ["Pacific Heights Pizza Bakery is a classic and historical place to meet new friends during winter." - {:small "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/small.jpg", - :medium "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/med.jpg", - :large "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/large.jpg"} - {:name "Pacific Heights Pizza Bakery", :categories ["Pizza" "Bakery"], :phone "415-006-0149", :id "7fda37a5-810f-4902-b571-54afe583f0dd"} - {:service "facebook", :facebook-photo-id "024c7142-3009-4fcc-9094-f7975b939e80", :url "http://facebook.com/photos/024c7142-3009-4fcc-9094-f7975b939e80"}] - ["Polk St. Mexican Coffee House is a horrible and underground place to have brunch with friends." - {:small "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/small.jpg", - :medium "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/med.jpg", - :large "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "flare", :username "lucky_pigeon"}] - ["Marina Low-Carb Food Truck is a swell and world-famous place to watch the Warriors game with friends." - {:small "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/small.jpg", - :medium "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/med.jpg", - :large "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "facebook", :facebook-photo-id "10073113-dee8-47d0-a994-eb84a3099ad4", :url "http://facebook.com/photos/10073113-dee8-47d0-a994-eb84a3099ad4"}] - ["Haight Soul Food Pop-Up Food Stand is a exclusive and amazing place to nurse a hangover during summer." - {:small "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/small.jpg", - :medium "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/med.jpg", - :large "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "yelp", :yelp-photo-id "59df02f3-5581-43bb-8875-ce689355302d", :categories ["Soul Food" "Pop-Up Food Stand"]}] - ["Polk St. Red White & Blue Café is a family-friendly and wonderful place to have brunch weekday afternoons." - {:small "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/small.jpg", - :medium "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/med.jpg", - :large "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "facebook", :facebook-photo-id "061a7423-a561-4ed6-9b75-20053659738c", :url "http://facebook.com/photos/061a7423-a561-4ed6-9b75-20053659738c"}] - ["Rasta's Paleo Churros is a classic and overrated place to catch a bite to eat in June." - {:small "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/small.jpg", - :medium "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/med.jpg", - :large "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "yelp", :yelp-photo-id "3326a855-2959-486f-a0f3-21c21211361f", :categories ["Paleo" "Churros"]}] - ["Nob Hill Korean Taqueria is a great and popular place to take a date when hungover." - {:small "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/small.jpg", - :medium "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/med.jpg", - :large "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "flare", :username "rasta_toucan"}] - ["Tenderloin Japanese Ice Cream Truck is a swell and acceptable place to nurse a hangover on Thursdays." - {:small "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/small.jpg", - :medium "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/med.jpg", - :large "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/large.jpg"} - {:name "Tenderloin Japanese Ice Cream Truck", :categories ["Japanese" "Ice Cream Truck"], :phone "415-856-0371", :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"} - {:service "yelp", :yelp-photo-id "cc977fda-1fb0-4e78-8699-1558c5f42e69", :categories ["Japanese" "Ice Cream Truck"]}] - ["Haight Soul Food Sushi is a decent and swell place to conduct a business meeting in the spring." - {:small "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/small.jpg", - :medium "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/med.jpg", - :large "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/large.jpg"} - {:name "Haight Soul Food Sushi", :categories ["Soul Food" "Sushi"], :phone "415-371-8026", :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"} - {:service "yelp", :yelp-photo-id "40052a53-f10e-4d38-806f-d88d2e77faf2", :categories ["Soul Food" "Sushi"]}] - ["Kyle's Low-Carb Grill is a classic and wonderful place to have a drink when hungover." - {:small "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/small.jpg", - :medium "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/med.jpg", - :large "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "flare", :username "mandy"}] - ["Rasta's Paleo Churros is a modern and horrible place to watch the Giants game on Taco Tuesday." - {:small "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/small.jpg", - :medium "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/med.jpg", - :large "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "foursquare", :foursquare-photo-id "ed2871fe-d033-4d81-87b1-86550a879d9c", :mayor "rasta_toucan"}] - ["Kyle's Chinese Restaurant is a delicious and well-decorated place to pitch an investor the first Sunday of the month." - {:small "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/small.jpg", - :medium "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/med.jpg", - :large "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/large.jpg"} - {:name "Kyle's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-298-9499", :id "de08b3c7-9929-40d8-8c20-dd9317613c17"} - {:service "yelp", :yelp-photo-id "964cf5f3-9d68-4fa5-8fff-93d09fe23f13", :categories ["Chinese" "Restaurant"]}] - ["Rasta's Paleo Café is a exclusive and popular place to sip Champagne on Saturday night." - {:small "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/small.jpg", - :medium "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/med.jpg", - :large "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/large.jpg"} - {:name "Rasta's Paleo Café", :categories ["Paleo" "Café"], :phone "415-392-6341", :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"} - {:service "facebook", :facebook-photo-id "0fc82427-48c6-468d-8813-9dca3289e519", :url "http://facebook.com/photos/0fc82427-48c6-468d-8813-9dca3289e519"}] - ["Kyle's Free-Range Taqueria is a horrible and exclusive place to sip a glass of expensive wine with your pet toucan." - {:small "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/small.jpg", - :medium "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/med.jpg", - :large "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/large.jpg"} - {:name "Kyle's Free-Range Taqueria", :categories ["Free-Range" "Taqueria"], :phone "415-201-7832", :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"} - {:service "foursquare", :foursquare-photo-id "6c35de08-10de-4ca4-acb8-a659d005f73c", :mayor "bob"}] - ["Sunset Deep-Dish Hotel & Restaurant is a modern and acceptable place to take visiting friends and relatives the second Saturday of the month." - {:small "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/small.jpg", - :medium "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/med.jpg", - :large "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/large.jpg"} - {:name "Sunset Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-332-0978", :id "a80745c7-af74-4579-8932-70dd488269e6"} - {:service "foursquare", :foursquare-photo-id "6b2657cc-f404-4507-99d0-89df941c8c19", :mayor "jessica"}] - ["Rasta's European Taqueria is a fantastic and great place to watch the Warriors game weekend mornings." - {:small "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/small.jpg", - :medium "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/med.jpg", - :large "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/large.jpg"} - {:name "Rasta's European Taqueria", :categories ["European" "Taqueria"], :phone "415-631-1599", :id "cb472880-ee6e-46e3-bd58-22cf33109aba"} - {:service "yelp", :yelp-photo-id "d2f7fc44-73f5-4c46-b6ac-b4407bb0df0e", :categories ["European" "Taqueria"]}] - ["Joe's Homestyle Eatery is a amazing and delicious place to have a drink when hungover." - {:small "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/small.jpg", - :medium "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/med.jpg", - :large "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "yelp", :yelp-photo-id "e5070b25-6933-44e9-a96b-bc7273c13616", :categories ["Homestyle" "Eatery"]}] - ["SoMa Japanese Churros is a acceptable and horrible place to have a birthday party with your pet dog." - {:small "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/small.jpg", - :medium "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/med.jpg", - :large "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "flare", :username "tupac"}] - ["Haight Soul Food Pop-Up Food Stand is a groovy and swell place to have breakfast when hungover." - {:small "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/small.jpg", - :medium "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/med.jpg", - :large "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "flare", :username "amy"}] - ["Mission Free-Range Liquor Store is a exclusive and horrible place to have a birthday party Friday nights." - {:small "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/small.jpg", - :medium "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/med.jpg", - :large "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/large.jpg"} - {:name "Mission Free-Range Liquor Store", :categories ["Free-Range" "Liquor Store"], :phone "415-041-3816", :id "6e665924-8e2c-42ab-af58-23a27f017e37"} - {:service "foursquare", :foursquare-photo-id "532267f2-2b66-4adc-8c51-b8ee4509e548", :mayor "kyle"}] - ["Rasta's Paleo Café is a delicious and delicious place to drink a craft beer in June." - {:small "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/small.jpg", - :medium "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/med.jpg", - :large "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/large.jpg"} - {:name "Rasta's Paleo Café", :categories ["Paleo" "Café"], :phone "415-392-6341", :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"} - {:service "facebook", :facebook-photo-id "7b6c29d2-078d-4feb-af95-88f10dc8937b", :url "http://facebook.com/photos/7b6c29d2-078d-4feb-af95-88f10dc8937b"}] - ["Mission Soul Food Pizzeria is a family-friendly and acceptable place to sip Champagne in June." - {:small "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/small.jpg", - :medium "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/med.jpg", - :large "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/large.jpg"} - {:name "Mission Soul Food Pizzeria", :categories ["Soul Food" "Pizzeria"], :phone "415-437-3479", :id "9905fe61-44cb-4626-843b-5d725c7949bb"} - {:service "twitter", :mentions ["@mission_soul_food_pizzeria"], :tags ["#soul" "#food" "#pizzeria"], :username "cam_saul"}] - ["Tenderloin Japanese Ice Cream Truck is a horrible and decent place to pitch an investor on Saturday night." - {:small "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/small.jpg", - :medium "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/med.jpg", - :large "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/large.jpg"} - {:name "Tenderloin Japanese Ice Cream Truck", :categories ["Japanese" "Ice Cream Truck"], :phone "415-856-0371", :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"} - {:service "flare", :username "bob"}] - ["Tenderloin Paleo Hotel & Restaurant is a decent and great place to drink a craft beer on Thursdays." - {:small "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/small.jpg", - :medium "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/med.jpg", - :large "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/large.jpg"} - {:name "Tenderloin Paleo Hotel & Restaurant", :categories ["Paleo" "Hotel & Restaurant"], :phone "415-402-1652", :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"} - {:service "flare", :username "amy"}] - ["Lucky's Low-Carb Coffee House is a horrible and historical place to take a date on a Tuesday afternoon." - {:small "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/small.jpg", - :medium "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/med.jpg", - :large "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/large.jpg"} - {:name "Lucky's Low-Carb Coffee House", :categories ["Low-Carb" "Coffee House"], :phone "415-145-7107", :id "81b0f944-f0ce-45e5-b84e-a924c441064a"} - {:service "flare", :username "kyle"}] - ["Nob Hill Korean Taqueria is a atmospheric and horrible place to sip Champagne on Saturday night." - {:small "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/small.jpg", - :medium "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/med.jpg", - :large "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "flare", :username "bob"}] - ["Rasta's Mexican Sushi is a popular and amazing place to nurse a hangover on a Tuesday afternoon." - {:small "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/small.jpg", - :medium "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/med.jpg", - :large "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/large.jpg"} - {:name "Rasta's Mexican Sushi", :categories ["Mexican" "Sushi"], :phone "415-387-1284", :id "e4912a22-e6ac-4806-8377-6497bf533a21"} - {:service "foursquare", :foursquare-photo-id "0bb00482-d502-4381-9efb-975487a53dc8", :mayor "cam_saul"}] - ["Haight Mexican Restaurant is a popular and underground place to drink a craft beer with your pet dog." - {:small "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/small.jpg", - :medium "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/med.jpg", - :large "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "foursquare", :foursquare-photo-id "9c94e761-e464-419e-8696-4670f1d9672c", :mayor "amy"}] - ["SoMa British Bakery is a swell and fantastic place to take visiting friends and relatives on Saturday night." - {:small "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/small.jpg", - :medium "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/med.jpg", - :large "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/large.jpg"} - {:name "SoMa British Bakery", :categories ["British" "Bakery"], :phone "415-909-5728", :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"} - {:service "flare", :username "jessica"}] - ["Market St. Gluten-Free Café is a historical and acceptable place to meet new friends during winter." - {:small "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/small.jpg", - :medium "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/med.jpg", - :large "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/large.jpg"} - {:name "Market St. Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-697-9776", :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"} - {:service "twitter", :mentions ["@market_st._gluten_free_café"], :tags ["#gluten-free" "#café"], :username "mandy"}] - ["Haight Soul Food Hotel & Restaurant is a amazing and underground place to watch the Warriors game in the spring." - {:small "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/small.jpg", - :medium "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/med.jpg", - :large "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "twitter", :mentions ["@haight_soul_food_hotel_&_restaurant"], :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], :username "joe"}] - ["Haight Chinese Gastro Pub is a world-famous and popular place to conduct a business meeting in July." - {:small "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/small.jpg", - :medium "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/med.jpg", - :large "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "yelp", :yelp-photo-id "d6adf74a-ac43-4a0d-8895-8a026f4577d6", :categories ["Chinese" "Gastro Pub"]}] - ["Polk St. Red White & Blue Café is a groovy and world-famous place to nurse a hangover on Taco Tuesday." - {:small "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/small.jpg", - :medium "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/med.jpg", - :large "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/large.jpg"} - {:name "Polk St. Red White & Blue Café", :categories ["Red White & Blue" "Café"], :phone "415-986-0661", :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"} - {:service "yelp", :yelp-photo-id "c4683200-8e6c-4e0f-801b-8b5c69263c82", :categories ["Red White & Blue" "Café"]}] - ["Rasta's Old-Fashioned Pop-Up Food Stand is a modern and great place to have a after-work cocktail after baseball games." - {:small "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/small.jpg", - :medium "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/med.jpg", - :large "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/large.jpg"} - {:name "Rasta's Old-Fashioned Pop-Up Food Stand", :categories ["Old-Fashioned" "Pop-Up Food Stand"], :phone "415-942-1875", :id "9fd8b920-a877-4888-86bf-578b2724ac4e"} - {:service "facebook", :facebook-photo-id "0a9c5d13-ab77-4b81-9404-78519055f74f", :url "http://facebook.com/photos/0a9c5d13-ab77-4b81-9404-78519055f74f"}] - ["Rasta's European Taqueria is a acceptable and overrated place to have brunch during winter." - {:small "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/small.jpg", - :medium "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/med.jpg", - :large "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/large.jpg"} - {:name "Rasta's European Taqueria", :categories ["European" "Taqueria"], :phone "415-631-1599", :id "cb472880-ee6e-46e3-bd58-22cf33109aba"} - {:service "flare", :username "lucky_pigeon"}] - ["Haight Soul Food Café is a overrated and historical place to watch the Warriors game when hungover." - {:small "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/small.jpg", - :medium "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/med.jpg", - :large "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "facebook", :facebook-photo-id "38bdb459-90a7-45d2-815b-ac50f4b945b2", :url "http://facebook.com/photos/38bdb459-90a7-45d2-815b-ac50f4b945b2"}] - ["Lower Pac Heights Cage-Free Coffee House is a exclusive and amazing place to pitch an investor with your pet dog." - {:small "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/small.jpg", - :medium "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/med.jpg", - :large "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "yelp", :yelp-photo-id "28eec82b-1a47-4e75-970f-ae118d14fcc0", :categories ["Cage-Free" "Coffee House"]}] - ["Haight Gormet Pizzeria is a overrated and popular place to have a after-work cocktail with your pet dog." - {:small "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/small.jpg", - :medium "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/med.jpg", - :large "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/large.jpg"} - {:name "Haight Gormet Pizzeria", :categories ["Gormet" "Pizzeria"], :phone "415-869-2197", :id "0425bdd0-3f57-4108-80e3-78335327355a"} - {:service "foursquare", :foursquare-photo-id "7ad05d8f-924b-4e77-8222-54704df41a83", :mayor "amy"}] - ["SF Deep-Dish Eatery is a exclusive and classic place to have a after-work cocktail the first Sunday of the month." - {:small "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/small.jpg", - :medium "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/med.jpg", - :large "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/large.jpg"} - {:name "SF Deep-Dish Eatery", :categories ["Deep-Dish" "Eatery"], :phone "415-476-9257", :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"} - {:service "facebook", :facebook-photo-id "e149be3f-de3b-4f90-a3d5-fa5a51362a45", :url "http://facebook.com/photos/e149be3f-de3b-4f90-a3d5-fa5a51362a45"}] - ["Cam's Soul Food Ice Cream Truck is a overrated and underground place to sip a glass of expensive wine on public holidays." - {:small "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/small.jpg", - :medium "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/med.jpg", - :large "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/large.jpg"} - {:name "Cam's Soul Food Ice Cream Truck", :categories ["Soul Food" "Ice Cream Truck"], :phone "415-270-8888", :id "f474e587-1801-43ea-93d5-4c4fd96460b8"} - {:service "yelp", :yelp-photo-id "75345d37-3a17-48ac-926c-707fdd1ed073", :categories ["Soul Food" "Ice Cream Truck"]}] - ["Haight Soul Food Hotel & Restaurant is a horrible and underappreciated place to have brunch during summer." - {:small "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/small.jpg", - :medium "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/med.jpg", - :large "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "twitter", :mentions ["@haight_soul_food_hotel_&_restaurant"], :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], :username "jane"}] - ["Kyle's Japanese Hotel & Restaurant is a well-decorated and underappreciated place to drink a craft beer weekday afternoons." - {:small "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/small.jpg", - :medium "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/med.jpg", - :large "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/large.jpg"} - {:name "Kyle's Japanese Hotel & Restaurant", :categories ["Japanese" "Hotel & Restaurant"], :phone "415-337-5387", :id "eced4f41-b627-4553-a297-888871038b69"} - {:service "facebook", :facebook-photo-id "7c8ab6a5-f832-4d16-9f5a-e5bb64991845", :url "http://facebook.com/photos/7c8ab6a5-f832-4d16-9f5a-e5bb64991845"}] - ["Sameer's GMO-Free Restaurant is a underappreciated and well-decorated place to take a date Friday nights." - {:small "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/small.jpg", - :medium "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/med.jpg", - :large "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/large.jpg"} - {:name "Sameer's GMO-Free Restaurant", :categories ["GMO-Free" "Restaurant"], :phone "415-128-9430", :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"} - {:service "flare", :username "rasta_toucan"}] - ["Kyle's Free-Range Taqueria is a groovy and classic place to pitch an investor the second Saturday of the month." - {:small "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/small.jpg", - :medium "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/med.jpg", - :large "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/large.jpg"} - {:name "Kyle's Free-Range Taqueria", :categories ["Free-Range" "Taqueria"], :phone "415-201-7832", :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"} - {:service "flare", :username "cam_saul"}] - ["Lucky's Gluten-Free Gastro Pub is a atmospheric and wonderful place to drink a craft beer on Thursdays." - {:small "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/small.jpg", - :medium "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/med.jpg", - :large "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "yelp", :yelp-photo-id "94ee2c9f-bfcc-462e-9893-6a740cd89edf", :categories ["Gluten-Free" "Gastro Pub"]}] - ["Cam's Mexican Gastro Pub is a fantastic and great place to nurse a hangover the second Saturday of the month." - {:small "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/small.jpg", - :medium "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/med.jpg", - :large "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "twitter", :mentions ["@cams_mexican_gastro_pub"], :tags ["#mexican" "#gastro" "#pub"], :username "cam_saul"}] - ["Marina Cage-Free Liquor Store is a classic and family-friendly place to watch the Giants game weekend evenings." - {:small "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/small.jpg", - :medium "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/med.jpg", - :large "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "yelp", :yelp-photo-id "3bd9b0c5-4bb3-492f-9f79-b95540dce0a5", :categories ["Cage-Free" "Liquor Store"]}] - ["Lucky's Cage-Free Liquor Store is a exclusive and classic place to catch a bite to eat with your pet dog." - {:small "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/small.jpg", - :medium "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/med.jpg", - :large "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/large.jpg"} - {:name "Lucky's Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-341-3219", :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"} - {:service "facebook", :facebook-photo-id "ed8f13c6-cb45-4dd5-ab2b-9e5261fe3e7b", :url "http://facebook.com/photos/ed8f13c6-cb45-4dd5-ab2b-9e5261fe3e7b"}] - ["Tenderloin Cage-Free Sushi is a well-decorated and popular place to have brunch on Saturday night." - {:small "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/small.jpg", - :medium "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/med.jpg", - :large "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "flare", :username "amy"}] - ["SoMa Old-Fashioned Pizzeria is a decent and well-decorated place to have a birthday party after baseball games." - {:small "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/small.jpg", - :medium "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/med.jpg", - :large "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/large.jpg"} - {:name "SoMa Old-Fashioned Pizzeria", :categories ["Old-Fashioned" "Pizzeria"], :phone "415-966-8856", :id "deb8997b-734d-402b-a181-bd888214bc86"} - {:service "twitter", :mentions ["@soma_old_fashioned_pizzeria"], :tags ["#old-fashioned" "#pizzeria"], :username "bob"}] - ["Marina Homestyle Pop-Up Food Stand is a groovy and great place to conduct a business meeting with your pet toucan." - {:small "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/small.jpg", - :medium "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/med.jpg", - :large "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/large.jpg"} - {:name "Marina Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-094-4567", :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"} - {:service "flare", :username "sameer"}] - ["Sameer's Pizza Liquor Store is a world-famous and underappreciated place to have breakfast the first Sunday of the month." - {:small "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/small.jpg", - :medium "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/med.jpg", - :large "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "foursquare", :foursquare-photo-id "a06600a6-ab71-4c47-bf7d-9334f0de14a6", :mayor "jessica"}] - ["Mission BBQ Churros is a underground and overrated place to have breakfast Friday nights." - {:small "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/small.jpg", - :medium "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/med.jpg", - :large "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/large.jpg"} - {:name "Mission BBQ Churros", :categories ["BBQ" "Churros"], :phone "415-406-5374", :id "429ea81a-02c5-449f-bfa7-03a11b227f1f"} - {:service "yelp", :yelp-photo-id "500335c8-a480-4d0a-b2e5-fd44f240c668", :categories ["BBQ" "Churros"]}] - ["Kyle's Old-Fashioned Pop-Up Food Stand is a amazing and underappreciated place to catch a bite to eat with friends." - {:small "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/small.jpg", - :medium "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/med.jpg", - :large "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/large.jpg"} - {:name "Kyle's Old-Fashioned Pop-Up Food Stand", :categories ["Old-Fashioned" "Pop-Up Food Stand"], :phone "415-638-8972", :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"} - {:service "foursquare", :foursquare-photo-id "dab0b46c-7062-4a40-8098-290e126c47df", :mayor "lucky_pigeon"}] - ["Nob Hill Free-Range Ice Cream Truck is a decent and classic place to meet new friends during winter." - {:small "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/small.jpg", - :medium "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/med.jpg", - :large "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/large.jpg"} - {:name "Nob Hill Free-Range Ice Cream Truck", :categories ["Free-Range" "Ice Cream Truck"], :phone "415-787-4049", :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"} - {:service "flare", :username "rasta_toucan"}] - ["Haight Chinese Gastro Pub is a world-famous and acceptable place to have a birthday party with your pet dog." - {:small "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/small.jpg", - :medium "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/med.jpg", - :large "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/large.jpg"} - {:name "Haight Chinese Gastro Pub", :categories ["Chinese" "Gastro Pub"], :phone "415-521-5825", :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"} - {:service "foursquare", :foursquare-photo-id "a061d2b6-8c85-4bf4-bc25-9450136340ca", :mayor "rasta_toucan"}] - ["Sameer's Pizza Liquor Store is a world-famous and historical place to catch a bite to eat after baseball games." - {:small "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/small.jpg", - :medium "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/med.jpg", - :large "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/large.jpg"} - {:name "Sameer's Pizza Liquor Store", :categories ["Pizza" "Liquor Store"], :phone "415-969-7474", :id "7b9c7dc3-d8f1-498d-843a-e62360449892"} - {:service "foursquare", :foursquare-photo-id "a45a7d9f-e866-4edf-9594-4229dc893000", :mayor "biggie"}] - ["Market St. Low-Carb Taqueria is a well-decorated and modern place to have breakfast weekday afternoons." - {:small "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/small.jpg", - :medium "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/med.jpg", - :large "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/large.jpg"} - {:name "Market St. Low-Carb Taqueria", :categories ["Low-Carb" "Taqueria"], :phone "415-751-6525", :id "f30eb85b-f048-4d8c-8008-3c2876125061"} - {:service "facebook", :facebook-photo-id "b02f088b-0b6e-4543-bb64-fc46173488c6", :url "http://facebook.com/photos/b02f088b-0b6e-4543-bb64-fc46173488c6"}] - ["Joe's Homestyle Eatery is a horrible and historical place to sip a glass of expensive wine Friday nights." - {:small "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/small.jpg", - :medium "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/med.jpg", - :large "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "twitter", :mentions ["@joes_homestyle_eatery"], :tags ["#homestyle" "#eatery"], :username "jane"}] - ["Market St. Homestyle Pop-Up Food Stand is a popular and delicious place to conduct a business meeting weekday afternoons." - {:small "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/small.jpg", - :medium "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/med.jpg", - :large "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/large.jpg"} - {:name "Market St. Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-213-3030", :id "2d873280-e43d-449e-9940-af96ae7df718"} - {:service "flare", :username "cam_saul"}] - ["Lucky's Afgan Sushi is a historical and fantastic place to catch a bite to eat in the spring." - {:small "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/small.jpg", - :medium "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/med.jpg", - :large "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/large.jpg"} - {:name "Lucky's Afgan Sushi", :categories ["Afgan" "Sushi"], :phone "415-188-3506", :id "4a47d0d2-0123-4bb9-b941-38702f0697e9"} - {:service "facebook", :facebook-photo-id "97487fd1-61b3-4492-9d4f-45ceba863252", :url "http://facebook.com/photos/97487fd1-61b3-4492-9d4f-45ceba863252"}] - ["Market St. Homestyle Pop-Up Food Stand is a overrated and wonderful place to drink a craft beer with your pet toucan." - {:small "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/small.jpg", - :medium "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/med.jpg", - :large "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/large.jpg"} - {:name "Market St. Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-213-3030", :id "2d873280-e43d-449e-9940-af96ae7df718"} - {:service "flare", :username "tupac"}] - ["Sunset Homestyle Grill is a acceptable and classic place to meet new friends in July." - {:small "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/small.jpg", - :medium "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/med.jpg", - :large "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "twitter", :mentions ["@sunset_homestyle_grill"], :tags ["#homestyle" "#grill"], :username "mandy"}] - ["Polk St. Mexican Coffee House is a overrated and great place to meet new friends on Thursdays." - {:small "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/small.jpg", - :medium "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/med.jpg", - :large "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/large.jpg"} - {:name "Polk St. Mexican Coffee House", :categories ["Mexican" "Coffee House"], :phone "415-144-7901", :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"} - {:service "twitter", :mentions ["@polk_st._mexican_coffee_house"], :tags ["#mexican" "#coffee" "#house"], :username "rasta_toucan"}] - ["Marina Low-Carb Food Truck is a exclusive and acceptable place to have breakfast on a Tuesday afternoon." - {:small "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/small.jpg", - :medium "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/med.jpg", - :large "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/large.jpg"} - {:name "Marina Low-Carb Food Truck", :categories ["Low-Carb" "Food Truck"], :phone "415-748-3513", :id "a13a5beb-19de-40ca-a334-02df3bdf5285"} - {:service "facebook", :facebook-photo-id "83ad3825-1e95-4078-9a3e-902802396114", :url "http://facebook.com/photos/83ad3825-1e95-4078-9a3e-902802396114"}] - ["Polk St. Korean Taqueria is a family-friendly and amazing place to conduct a business meeting the second Saturday of the month." - {:small "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/small.jpg", - :medium "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/med.jpg", - :large "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "foursquare", :foursquare-photo-id "ade689fc-821b-4c22-8e66-cc48a2e4d7b9", :mayor "sameer"}] - ["Kyle's Japanese Hotel & Restaurant is a world-famous and world-famous place to have a drink Friday nights." - {:small "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/small.jpg", - :medium "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/med.jpg", - :large "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/large.jpg"} - {:name "Kyle's Japanese Hotel & Restaurant", :categories ["Japanese" "Hotel & Restaurant"], :phone "415-337-5387", :id "eced4f41-b627-4553-a297-888871038b69"} - {:service "facebook", :facebook-photo-id "e6a59e30-0ffd-4a6f-b4bc-f45ba8c214cf", :url "http://facebook.com/photos/e6a59e30-0ffd-4a6f-b4bc-f45ba8c214cf"}] - ["Mission Soul Food Pizzeria is a swell and swell place to pitch an investor with your pet toucan." - {:small "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/small.jpg", - :medium "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/med.jpg", - :large "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/large.jpg"} - {:name "Mission Soul Food Pizzeria", :categories ["Soul Food" "Pizzeria"], :phone "415-437-3479", :id "9905fe61-44cb-4626-843b-5d725c7949bb"} - {:service "yelp", :yelp-photo-id "0e7ee5ce-7a90-4431-959f-948658ec504c", :categories ["Soul Food" "Pizzeria"]}] - ["Alcatraz Pizza Churros is a fantastic and world-famous place to watch the Giants game in the spring." - {:small "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/small.jpg", - :medium "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/med.jpg", - :large "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/large.jpg"} - {:name "Alcatraz Pizza Churros", :categories ["Pizza" "Churros"], :phone "415-754-7867", :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"} - {:service "foursquare", :foursquare-photo-id "645a21e2-d948-4e57-a2c2-a29c6a6a32d8", :mayor "lucky_pigeon"}] - ["Marina No-MSG Sushi is a wonderful and family-friendly place to have a birthday party in July." - {:small "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/small.jpg", - :medium "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/med.jpg", - :large "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/large.jpg"} - {:name "Marina No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-856-5937", :id "d51013a3-8547-4705-a5f0-cb11d8206481"} - {:service "twitter", :mentions ["@marina_no_msg_sushi"], :tags ["#no-msg" "#sushi"], :username "sameer"}] - ["Haight Soul Food Sushi is a groovy and historical place to have a drink weekend evenings." - {:small "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/small.jpg", - :medium "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/med.jpg", - :large "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/large.jpg"} - {:name "Haight Soul Food Sushi", :categories ["Soul Food" "Sushi"], :phone "415-371-8026", :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"} - {:service "flare", :username "tupac"}] - ["SF British Pop-Up Food Stand is a horrible and amazing place to drink a craft beer with your pet dog." - {:small "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/small.jpg", - :medium "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/med.jpg", - :large "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/large.jpg"} - {:name "SF British Pop-Up Food Stand", :categories ["British" "Pop-Up Food Stand"], :phone "415-441-3725", :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"} - {:service "yelp", :yelp-photo-id "e60013b6-4660-4767-8500-d55ccf154362", :categories ["British" "Pop-Up Food Stand"]}] - ["Haight Soul Food Café is a amazing and family-friendly place to take visiting friends and relatives on Saturday night." - {:small "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/small.jpg", - :medium "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/med.jpg", - :large "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/large.jpg"} - {:name "Haight Soul Food Café", :categories ["Soul Food" "Café"], :phone "415-257-1769", :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"} - {:service "flare", :username "sameer"}] - ["Oakland European Liquor Store is a wonderful and underappreciated place to people-watch in July." - {:small "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/small.jpg", - :medium "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/med.jpg", - :large "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/large.jpg"} - {:name "Oakland European Liquor Store", :categories ["European" "Liquor Store"], :phone "415-559-1516", :id "e342e7b7-e82d-475d-a822-b2df9c84850d"} - {:service "flare", :username "rasta_toucan"}] - ["Nob Hill Free-Range Ice Cream Truck is a atmospheric and world-famous place to have brunch on Saturday night." - {:small "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/small.jpg", - :medium "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/med.jpg", - :large "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/large.jpg"} - {:name "Nob Hill Free-Range Ice Cream Truck", :categories ["Free-Range" "Ice Cream Truck"], :phone "415-787-4049", :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"} - {:service "flare", :username "jessica"}] - ["Lucky's Gluten-Free Gastro Pub is a family-friendly and family-friendly place to watch the Warriors game weekday afternoons." - {:small "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/small.jpg", - :medium "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/med.jpg", - :large "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/large.jpg"} - {:name "Lucky's Gluten-Free Gastro Pub", :categories ["Gluten-Free" "Gastro Pub"], :phone "415-391-6443", :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"} - {:service "facebook", :facebook-photo-id "1be63daf-2a0c-4514-a7a3-d20264346b9d", :url "http://facebook.com/photos/1be63daf-2a0c-4514-a7a3-d20264346b9d"}] - ["Sunset American Churros is a well-decorated and horrible place to watch the Giants game on public holidays." - {:small "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/small.jpg", - :medium "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/med.jpg", - :large "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "yelp", :yelp-photo-id "2852b37d-d922-4259-be12-744fa4bfee02", :categories ["American" "Churros"]}] - ["Kyle's European Churros is a family-friendly and fantastic place to have brunch in July." - {:small "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/small.jpg", - :medium "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/med.jpg", - :large "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/large.jpg"} - {:name "Kyle's European Churros", :categories ["European" "Churros"], :phone "415-233-8392", :id "5270240c-6e6e-4512-9344-3dc497d6ea49"} - {:service "yelp", :yelp-photo-id "c151203a-b7d1-45c7-89c5-f874054cb524", :categories ["European" "Churros"]}] - ["Tenderloin Cage-Free Sushi is a world-famous and underground place to drink a craft beer on a Tuesday afternoon." - {:small "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/small.jpg", - :medium "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/med.jpg", - :large "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "twitter", :mentions ["@tenderloin_cage_free_sushi"], :tags ["#cage-free" "#sushi"], :username "sameer"}] - ["Kyle's Japanese Hotel & Restaurant is a wonderful and decent place to catch a bite to eat with friends." - {:small "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/small.jpg", - :medium "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/med.jpg", - :large "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/large.jpg"} - {:name "Kyle's Japanese Hotel & Restaurant", :categories ["Japanese" "Hotel & Restaurant"], :phone "415-337-5387", :id "eced4f41-b627-4553-a297-888871038b69"} - {:service "foursquare", :foursquare-photo-id "7efa0b90-41f8-42b4-b148-b981eb3d2c0f", :mayor "cam_saul"}] - ["Haight Soul Food Pop-Up Food Stand is a decent and atmospheric place to nurse a hangover weekend mornings." - {:small "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/small.jpg", - :medium "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/med.jpg", - :large "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/large.jpg"} - {:name "Haight Soul Food Pop-Up Food Stand", :categories ["Soul Food" "Pop-Up Food Stand"], :phone "415-741-8726", :id "9735184b-1299-410f-a98e-10d9c548af42"} - {:service "foursquare", :foursquare-photo-id "87cbe196-2aa6-45d1-87b8-bf9027c655c1", :mayor "cam_saul"}] - ["Tenderloin Gluten-Free Bar & Grill is a popular and wonderful place to sip Champagne on Saturday night." - {:small "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/small.jpg", - :medium "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/med.jpg", - :large "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/large.jpg"} - {:name "Tenderloin Gluten-Free Bar & Grill", :categories ["Gluten-Free" "Bar & Grill"], :phone "415-904-0956", :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"} - {:service "facebook", :facebook-photo-id "07b3bf42-0187-4f24-9d2a-b6cb5419fe6b", :url "http://facebook.com/photos/07b3bf42-0187-4f24-9d2a-b6cb5419fe6b"}] - ["Lower Pac Heights Deep-Dish Ice Cream Truck is a amazing and family-friendly place to have a birthday party Friday nights." - {:small "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/small.jpg", - :medium "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/med.jpg", - :large "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", :categories ["Deep-Dish" "Ice Cream Truck"], :phone "415-495-1414", :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"} - {:service "yelp", :yelp-photo-id "d8cfa65d-018c-4a07-baa5-ef38e019b53e", :categories ["Deep-Dish" "Ice Cream Truck"]}] - ["Sunset American Churros is a historical and swell place to catch a bite to eat on a Tuesday afternoon." - {:small "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/small.jpg", - :medium "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/med.jpg", - :large "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "yelp", :yelp-photo-id "e73b3ca1-da16-42f7-99ed-11e978abae41", :categories ["American" "Churros"]}] - ["Kyle's Low-Carb Grill is a decent and great place to watch the Warriors game in the fall." - {:small "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/small.jpg", - :medium "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/med.jpg", - :large "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "foursquare", :foursquare-photo-id "abf79d51-a26b-48f8-8267-28af692327b7", :mayor "lucky_pigeon"}] - ["Pacific Heights No-MSG Sushi is a world-famous and groovy place to have breakfast during summer." - {:small "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/small.jpg", - :medium "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/med.jpg", - :large "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/large.jpg"} - {:name "Pacific Heights No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-354-9547", :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"} - {:service "facebook", :facebook-photo-id "24bc49af-3dd9-4d57-bd9c-204aacd68eea", :url "http://facebook.com/photos/24bc49af-3dd9-4d57-bd9c-204aacd68eea"}] - ["Lower Pac Heights Deep-Dish Liquor Store is a delicious and underappreciated place to have a after-work cocktail in the fall." - {:small "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/small.jpg", - :medium "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/med.jpg", - :large "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/large.jpg"} - {:name "Lower Pac Heights Deep-Dish Liquor Store", :categories ["Deep-Dish" "Liquor Store"], :phone "415-497-3039", :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"} - {:service "foursquare", :foursquare-photo-id "c57cddd8-4867-43cf-8f71-892e87788b73", :mayor "jessica"}] - ["Pacific Heights Free-Range Eatery is a decent and acceptable place to take visiting friends and relatives Friday nights." - {:small "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/small.jpg", - :medium "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/med.jpg", - :large "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "twitter", :mentions ["@pacific_heights_free_range_eatery"], :tags ["#free-range" "#eatery"], :username "mandy"}] - ["Marina Cage-Free Liquor Store is a popular and wonderful place to take visiting friends and relatives weekend mornings." - {:small "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/small.jpg", - :medium "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/med.jpg", - :large "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/large.jpg"} - {:name "Marina Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-571-0783", :id "ad68f549-3000-407e-ab98-7f314cfa4653"} - {:service "yelp", :yelp-photo-id "6301ba32-41f7-44ff-872e-3c6f96e34ebb", :categories ["Cage-Free" "Liquor Store"]}] - ["Mission Japanese Coffee House is a popular and swell place to have a drink on public holidays." - {:small "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/small.jpg", - :medium "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/med.jpg", - :large "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/large.jpg"} - {:name "Mission Japanese Coffee House", :categories ["Japanese" "Coffee House"], :phone "415-561-0506", :id "60dd274e-0cbf-4521-946d-8a4e0f151150"} - {:service "yelp", :yelp-photo-id "7c9bc27a-7478-4bec-b6e1-99657346b360", :categories ["Japanese" "Coffee House"]}] - ["Joe's Homestyle Eatery is a swell and groovy place to take a date in June." - {:small "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/small.jpg", - :medium "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/med.jpg", - :large "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "twitter", :mentions ["@joes_homestyle_eatery"], :tags ["#homestyle" "#eatery"], :username "sameer"}] - ["Nob Hill Free-Range Ice Cream Truck is a well-decorated and modern place to watch the Giants game after baseball games." - {:small "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/small.jpg", - :medium "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/med.jpg", - :large "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/large.jpg"} - {:name "Nob Hill Free-Range Ice Cream Truck", :categories ["Free-Range" "Ice Cream Truck"], :phone "415-787-4049", :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"} - {:service "twitter", :mentions ["@nob_hill_free_range_ice_cream_truck"], :tags ["#free-range" "#ice" "#cream" "#truck"], :username "jane"}] - ["Lucky's Gluten-Free Café is a horrible and historical place to conduct a business meeting on Saturday night." - {:small "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/small.jpg", - :medium "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/med.jpg", - :large "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/large.jpg"} - {:name "Lucky's Gluten-Free Café", :categories ["Gluten-Free" "Café"], :phone "415-740-2328", :id "379af987-ad40-4a93-88a6-0233e1c14649"} - {:service "facebook", :facebook-photo-id "f37c703b-0e19-4b91-9fc0-3945524300ed", :url "http://facebook.com/photos/f37c703b-0e19-4b91-9fc0-3945524300ed"}] - ["Kyle's Low-Carb Grill is a decent and underground place to have brunch on Taco Tuesday." - {:small "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/small.jpg", - :medium "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/med.jpg", - :large "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "flare", :username "kyle"}] - ["Rasta's Paleo Churros is a popular and family-friendly place to conduct a business meeting on a Tuesday afternoon." - {:small "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/small.jpg", - :medium "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/med.jpg", - :large "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/large.jpg"} - {:name "Rasta's Paleo Churros", :categories ["Paleo" "Churros"], :phone "415-915-0309", :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"} - {:service "flare", :username "amy"}] - ["Kyle's Old-Fashioned Pop-Up Food Stand is a world-famous and family-friendly place to have a drink with your pet dog." - {:small "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/small.jpg", - :medium "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/med.jpg", - :large "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/large.jpg"} - {:name "Kyle's Old-Fashioned Pop-Up Food Stand", :categories ["Old-Fashioned" "Pop-Up Food Stand"], :phone "415-638-8972", :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"} - {:service "foursquare", :foursquare-photo-id "e99162db-5fc6-4164-9537-a35aee30f51c", :mayor "kyle"}] - ["Joe's Homestyle Eatery is a horrible and exclusive place to watch the Giants game the first Sunday of the month." - {:small "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/small.jpg", - :medium "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/med.jpg", - :large "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/large.jpg"} - {:name "Joe's Homestyle Eatery", :categories ["Homestyle" "Eatery"], :phone "415-950-1337", :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"} - {:service "facebook", :facebook-photo-id "87163e8d-0bcf-4d20-b3a8-ab895ddfef15", :url "http://facebook.com/photos/87163e8d-0bcf-4d20-b3a8-ab895ddfef15"}] - ["Lucky's Deep-Dish Gastro Pub is a horrible and underground place to have a birthday party with your pet dog." - {:small "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/small.jpg", - :medium "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/med.jpg", - :large "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/large.jpg"} - {:name "Lucky's Deep-Dish Gastro Pub", :categories ["Deep-Dish" "Gastro Pub"], :phone "415-487-4085", :id "0136c454-0968-41cd-a237-ceec5724cab8"} - {:service "foursquare", :foursquare-photo-id "08c45fbc-d085-4e2d-8b69-3e2d89f457a9", :mayor "amy"}] - ["Sameer's GMO-Free Pop-Up Food Stand is a swell and world-famous place to take visiting friends and relatives during summer." - {:small "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/small.jpg", - :medium "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/med.jpg", - :large "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/large.jpg"} - {:name "Sameer's GMO-Free Pop-Up Food Stand", :categories ["GMO-Free" "Pop-Up Food Stand"], :phone "415-217-7891", :id "a829efc7-7e03-4e73-b072-83d10d1e3953"} - {:service "twitter", :mentions ["@sameers_gmo_free_pop_up_food_stand"], :tags ["#gmo-free" "#pop-up" "#food" "#stand"], :username "joe"}] - ["Nob Hill Korean Taqueria is a classic and classic place to take a date after baseball games." - {:small "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/small.jpg", - :medium "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/med.jpg", - :large "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "twitter", :mentions ["@nob_hill_korean_taqueria"], :tags ["#korean" "#taqueria"], :username "amy"}] - ["Lucky's Afgan Sushi is a swell and amazing place to drink a craft beer the second Saturday of the month." - {:small "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/small.jpg", - :medium "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/med.jpg", - :large "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/large.jpg"} - {:name "Lucky's Afgan Sushi", :categories ["Afgan" "Sushi"], :phone "415-188-3506", :id "4a47d0d2-0123-4bb9-b941-38702f0697e9"} - {:service "facebook", :facebook-photo-id "ba479234-45d6-4e25-aba9-5f820f0ba318", :url "http://facebook.com/photos/ba479234-45d6-4e25-aba9-5f820f0ba318"}] - ["Lucky's Deep-Dish Gastro Pub is a delicious and amazing place to sip a glass of expensive wine in June." - {:small "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/small.jpg", - :medium "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/med.jpg", - :large "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/large.jpg"} - {:name "Lucky's Deep-Dish Gastro Pub", :categories ["Deep-Dish" "Gastro Pub"], :phone "415-487-4085", :id "0136c454-0968-41cd-a237-ceec5724cab8"} - {:service "foursquare", :foursquare-photo-id "8c374317-71fe-4112-ab02-53450a771d48", :mayor "joe"}] - ["Pacific Heights Free-Range Eatery is a exclusive and wonderful place to pitch an investor in July." - {:small "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/small.jpg", - :medium "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/med.jpg", - :large "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/large.jpg"} - {:name "Pacific Heights Free-Range Eatery", :categories ["Free-Range" "Eatery"], :phone "415-901-6541", :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"} - {:service "flare", :username "cam_saul"}] - ["Tenderloin Cage-Free Sushi is a fantastic and modern place to take a date during summer." - {:small "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/small.jpg", - :medium "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/med.jpg", - :large "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "twitter", :mentions ["@tenderloin_cage_free_sushi"], :tags ["#cage-free" "#sushi"], :username "sameer"}] - ["Polk St. Japanese Liquor Store is a delicious and decent place to drink a craft beer the first Sunday of the month." - {:small "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/small.jpg", - :medium "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/med.jpg", - :large "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/large.jpg"} - {:name "Polk St. Japanese Liquor Store", :categories ["Japanese" "Liquor Store"], :phone "415-726-7986", :id "b57ceac5-328d-4b65-9909-a1f9abc93015"} - {:service "facebook", :facebook-photo-id "4e3e3372-d489-4ec6-9b80-c3615d7bb110", :url "http://facebook.com/photos/4e3e3372-d489-4ec6-9b80-c3615d7bb110"}] - ["Sunset Homestyle Grill is a overrated and popular place to have a after-work cocktail weekday afternoons." - {:small "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/small.jpg", - :medium "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/med.jpg", - :large "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "facebook", :facebook-photo-id "e40f9858-278e-4b0f-aa49-57295cdad381", :url "http://facebook.com/photos/e40f9858-278e-4b0f-aa49-57295cdad381"}] - ["Cam's Old-Fashioned Coffee House is a groovy and popular place to conduct a business meeting weekend evenings." - {:small "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/small.jpg", - :medium "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/med.jpg", - :large "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-868-2973", :id "27592c2b-e682-44bb-be28-8e9a622becca"} - {:service "flare", :username "jane"}] - ["Joe's Modern Coffee House is a swell and swell place to catch a bite to eat in the spring." - {:small "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/small.jpg", - :medium "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/med.jpg", - :large "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/large.jpg"} - {:name "Joe's Modern Coffee House", :categories ["Modern" "Coffee House"], :phone "415-331-5269", :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"} - {:service "foursquare", :foursquare-photo-id "a81b8c97-cde1-40d8-8a54-05fc2c6074b4", :mayor "rasta_toucan"}] - ["Marina Homestyle Pop-Up Food Stand is a exclusive and modern place to have brunch the second Saturday of the month." - {:small "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/small.jpg", - :medium "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/med.jpg", - :large "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/large.jpg"} - {:name "Marina Homestyle Pop-Up Food Stand", :categories ["Homestyle" "Pop-Up Food Stand"], :phone "415-094-4567", :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"} - {:service "twitter", :mentions ["@marina_homestyle_pop_up_food_stand"], :tags ["#homestyle" "#pop-up" "#food" "#stand"], :username "biggie"}] - ["Mission Japanese Coffee House is a amazing and decent place to catch a bite to eat on public holidays." - {:small "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/small.jpg", - :medium "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/med.jpg", - :large "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/large.jpg"} - {:name "Mission Japanese Coffee House", :categories ["Japanese" "Coffee House"], :phone "415-561-0506", :id "60dd274e-0cbf-4521-946d-8a4e0f151150"} - {:service "foursquare", :foursquare-photo-id "c628a149-96fc-4ece-a89c-d07f0d57bc83", :mayor "biggie"}] - ["Kyle's Free-Range Taqueria is a underground and groovy place to have a drink during summer." - {:small "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/small.jpg", - :medium "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/med.jpg", - :large "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/large.jpg"} - {:name "Kyle's Free-Range Taqueria", :categories ["Free-Range" "Taqueria"], :phone "415-201-7832", :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"} - {:service "foursquare", :foursquare-photo-id "29bdfc7c-9766-463f-a8c0-4568fa583604", :mayor "bob"}] - ["Sameer's GMO-Free Pop-Up Food Stand is a delicious and family-friendly place to people-watch weekday afternoons." - {:small "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/small.jpg", - :medium "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/med.jpg", - :large "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/large.jpg"} - {:name "Sameer's GMO-Free Pop-Up Food Stand", :categories ["GMO-Free" "Pop-Up Food Stand"], :phone "415-217-7891", :id "a829efc7-7e03-4e73-b072-83d10d1e3953"} - {:service "facebook", :facebook-photo-id "8a7c76a5-9099-4e2e-b6d8-8eebe10acf72", :url "http://facebook.com/photos/8a7c76a5-9099-4e2e-b6d8-8eebe10acf72"}] - ["Mission Japanese Coffee House is a underappreciated and delicious place to drink a craft beer on public holidays." - {:small "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/small.jpg", - :medium "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/med.jpg", - :large "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/large.jpg"} - {:name "Mission Japanese Coffee House", :categories ["Japanese" "Coffee House"], :phone "415-561-0506", :id "60dd274e-0cbf-4521-946d-8a4e0f151150"} - {:service "yelp", :yelp-photo-id "ec651838-e2d7-45c1-8b8a-70f80f87fae5", :categories ["Japanese" "Coffee House"]}] - ["Cam's Mexican Gastro Pub is a historical and underappreciated place to conduct a business meeting with friends." - {:small "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/small.jpg", - :medium "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/med.jpg", - :large "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/large.jpg"} - {:name "Cam's Mexican Gastro Pub", :categories ["Mexican" "Gastro Pub"], :phone "415-320-9123", :id "bb958ac5-758e-4f42-b984-6b0e13f25194"} - {:service "twitter", :mentions ["@cams_mexican_gastro_pub"], :tags ["#mexican" "#gastro" "#pub"], :username "kyle"}] - ["Haight Mexican Restaurant is a well-decorated and popular place to have breakfast Friday nights." - {:small "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/small.jpg", - :medium "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/med.jpg", - :large "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "twitter", :mentions ["@haight_mexican_restaurant"], :tags ["#mexican" "#restaurant"], :username "bob"}] - ["Chinatown Paleo Food Truck is a great and underappreciated place to take a date in the spring." - {:small "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/small.jpg", - :medium "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/med.jpg", - :large "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "twitter", :mentions ["@chinatown_paleo_food_truck"], :tags ["#paleo" "#food" "#truck"], :username "bob"}] - ["Lucky's Cage-Free Liquor Store is a overrated and fantastic place to have a birthday party in the fall." - {:small "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/small.jpg", - :medium "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/med.jpg", - :large "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/large.jpg"} - {:name "Lucky's Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-341-3219", :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"} - {:service "flare", :username "sameer"}] - ["Pacific Heights No-MSG Sushi is a horrible and horrible place to people-watch the second Saturday of the month." - {:small "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/small.jpg", - :medium "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/med.jpg", - :large "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/large.jpg"} - {:name "Pacific Heights No-MSG Sushi", :categories ["No-MSG" "Sushi"], :phone "415-354-9547", :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"} - {:service "facebook", :facebook-photo-id "362ae2f8-3bca-4a80-8b34-708aaac74139", :url "http://facebook.com/photos/362ae2f8-3bca-4a80-8b34-708aaac74139"}] - ["Alcatraz Pizza Churros is a swell and acceptable place to catch a bite to eat Friday nights." - {:small "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/small.jpg", - :medium "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/med.jpg", - :large "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/large.jpg"} - {:name "Alcatraz Pizza Churros", :categories ["Pizza" "Churros"], :phone "415-754-7867", :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"} - {:service "facebook", :facebook-photo-id "8ab454e9-36ae-4824-a60b-bcacaaa56a76", :url "http://facebook.com/photos/8ab454e9-36ae-4824-a60b-bcacaaa56a76"}] - ["Marina Modern Bar & Grill is a exclusive and world-famous place to catch a bite to eat weekend evenings." - {:small "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/small.jpg", - :medium "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/med.jpg", - :large "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/large.jpg"} - {:name "Marina Modern Bar & Grill", :categories ["Modern" "Bar & Grill"], :phone "415-203-8530", :id "806144f1-bb7a-4271-8fcb-fc6550f51676"} - {:service "flare", :username "amy"}] - ["Rasta's British Food Truck is a amazing and great place to sip Champagne on a Tuesday afternoon." - {:small "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/small.jpg", - :medium "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/med.jpg", - :large "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/large.jpg"} - {:name "Rasta's British Food Truck", :categories ["British" "Food Truck"], :phone "415-958-9031", :id "b6616c97-01d0-488f-a855-bcd6efe2b899"} - {:service "foursquare", :foursquare-photo-id "bf141876-b610-4fcb-a1c2-d292d8989929", :mayor "amy"}] - ["Lucky's Deep-Dish Gastro Pub is a classic and well-decorated place to have a birthday party in the spring." - {:small "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/small.jpg", - :medium "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/med.jpg", - :large "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/large.jpg"} - {:name "Lucky's Deep-Dish Gastro Pub", :categories ["Deep-Dish" "Gastro Pub"], :phone "415-487-4085", :id "0136c454-0968-41cd-a237-ceec5724cab8"} - {:service "twitter", :mentions ["@luckys_deep_dish_gastro_pub"], :tags ["#deep-dish" "#gastro" "#pub"], :username "cam_saul"}] - ["Sameer's Chinese Restaurant is a underappreciated and popular place to watch the Warriors game Friday nights." - {:small "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/small.jpg", - :medium "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/med.jpg", - :large "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/large.jpg"} - {:name "Sameer's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-707-3659", :id "51a9545e-7e1e-40f1-b550-09067b648f20"} - {:service "foursquare", :foursquare-photo-id "f7875634-d317-4cb5-a04a-cf6babd14144", :mayor "rasta_toucan"}] - ["Nob Hill Korean Taqueria is a well-decorated and underappreciated place to catch a bite to eat on Thursdays." - {:small "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/small.jpg", - :medium "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/med.jpg", - :large "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/large.jpg"} - {:name "Nob Hill Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-107-7332", :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"} - {:service "foursquare", :foursquare-photo-id "ffbd6d15-24cf-4dd3-a168-40ac03cd2f18", :mayor "rasta_toucan"}] - ["SoMa British Bakery is a fantastic and modern place to take a date on public holidays." - {:small "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/small.jpg", - :medium "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/med.jpg", - :large "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/large.jpg"} - {:name "SoMa British Bakery", :categories ["British" "Bakery"], :phone "415-909-5728", :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"} - {:service "facebook", :facebook-photo-id "9ecd820a-7017-4be2-a4b1-18ab0da5e233", :url "http://facebook.com/photos/9ecd820a-7017-4be2-a4b1-18ab0da5e233"}] - ["Haight Mexican Restaurant is a exclusive and swell place to nurse a hangover on a Tuesday afternoon." - {:small "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/small.jpg", - :medium "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/med.jpg", - :large "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "foursquare", :foursquare-photo-id "4a084c4a-884f-4907-9b96-390b543cb03f", :mayor "biggie"}] - ["Sameer's Chinese Restaurant is a horrible and well-decorated place to have a drink on public holidays." - {:small "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/small.jpg", - :medium "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/med.jpg", - :large "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/large.jpg"} - {:name "Sameer's Chinese Restaurant", :categories ["Chinese" "Restaurant"], :phone "415-707-3659", :id "51a9545e-7e1e-40f1-b550-09067b648f20"} - {:service "facebook", :facebook-photo-id "2f1c35f8-83fb-4c81-a92c-00a81d9c93de", :url "http://facebook.com/photos/2f1c35f8-83fb-4c81-a92c-00a81d9c93de"}] - ["Chinatown Paleo Food Truck is a groovy and decent place to drink a craft beer during summer." - {:small "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/small.jpg", - :medium "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/med.jpg", - :large "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "foursquare", :foursquare-photo-id "62fdb59c-0629-4adb-b4e1-6a6cb7c7e280", :mayor "mandy"}] - ["Rasta's Mexican Sushi is a acceptable and exclusive place to have a drink weekday afternoons." - {:small "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/small.jpg", - :medium "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/med.jpg", - :large "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/large.jpg"} - {:name "Rasta's Mexican Sushi", :categories ["Mexican" "Sushi"], :phone "415-387-1284", :id "e4912a22-e6ac-4806-8377-6497bf533a21"} - {:service "foursquare", :foursquare-photo-id "089ff188-9374-4b13-93e6-005c9767dabb", :mayor "bob"}] - ["Haight Mexican Restaurant is a modern and groovy place to watch the Warriors game on a Tuesday afternoon." - {:small "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/small.jpg", - :medium "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/med.jpg", - :large "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/large.jpg"} - {:name "Haight Mexican Restaurant", :categories ["Mexican" "Restaurant"], :phone "415-758-8690", :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"} - {:service "yelp", :yelp-photo-id "42494912-52c5-4492-83c8-76454f465d99", :categories ["Mexican" "Restaurant"]}] - ["Nob Hill Gluten-Free Coffee House is a horrible and classic place to people-watch during summer." - {:small "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/small.jpg", - :medium "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/med.jpg", - :large "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "facebook", :facebook-photo-id "d326a510-ac21-46f3-8474-078ef511cc54", :url "http://facebook.com/photos/d326a510-ac21-46f3-8474-078ef511cc54"}] - ["Mission British Café is a underground and groovy place to have brunch weekday afternoons." - {:small "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/small.jpg", - :medium "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/med.jpg", - :large "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/large.jpg"} - {:name "Mission British Café", :categories ["British" "Café"], :phone "415-715-7004", :id "c99899e3-439c-4444-9dc4-5598632aec8d"} - {:service "foursquare", :foursquare-photo-id "1df360d1-5d63-4a28-9c37-966dc7e616f0", :mayor "joe"}] - ["SoMa Japanese Churros is a classic and popular place to watch the Warriors game on public holidays." - {:small "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/small.jpg", - :medium "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/med.jpg", - :large "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "foursquare", :foursquare-photo-id "2dda53d5-9b31-4f4e-b36b-e37b45e67de7", :mayor "biggie"}] - ["Haight Soul Food Hotel & Restaurant is a amazing and acceptable place to have breakfast weekday afternoons." - {:small "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/small.jpg", - :medium "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/med.jpg", - :large "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/large.jpg"} - {:name "Haight Soul Food Hotel & Restaurant", :categories ["Soul Food" "Hotel & Restaurant"], :phone "415-786-9541", :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"} - {:service "flare", :username "bob"}] - ["Lower Pac Heights Cage-Free Coffee House is a atmospheric and well-decorated place to have brunch weekend mornings." - {:small "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/small.jpg", - :medium "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/med.jpg", - :large "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/large.jpg"} - {:name "Lower Pac Heights Cage-Free Coffee House", :categories ["Cage-Free" "Coffee House"], :phone "415-697-9309", :id "02b1f618-41a0-406b-96dd-1a017f630b81"} - {:service "facebook", :facebook-photo-id "01dcf27f-6233-415d-8b8b-4fe7a841b457", :url "http://facebook.com/photos/01dcf27f-6233-415d-8b8b-4fe7a841b457"}] - ["Haight Gormet Pizzeria is a fantastic and modern place to pitch an investor during winter." - {:small "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/small.jpg", - :medium "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/med.jpg", - :large "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/large.jpg"} - {:name "Haight Gormet Pizzeria", :categories ["Gormet" "Pizzeria"], :phone "415-869-2197", :id "0425bdd0-3f57-4108-80e3-78335327355a"} - {:service "yelp", :yelp-photo-id "ad5aa48a-bdda-4396-86f3-1dc63b40a0d1", :categories ["Gormet" "Pizzeria"]}] - ["Rasta's Paleo Café is a wonderful and family-friendly place to have a drink on Taco Tuesday." - {:small "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/small.jpg", - :medium "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/med.jpg", - :large "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/large.jpg"} - {:name "Rasta's Paleo Café", :categories ["Paleo" "Café"], :phone "415-392-6341", :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"} - {:service "facebook", :facebook-photo-id "f62fb4d0-d254-4496-a5c9-01ed4f7a3651", :url "http://facebook.com/photos/f62fb4d0-d254-4496-a5c9-01ed4f7a3651"}] - ["Kyle's Low-Carb Grill is a horrible and popular place to nurse a hangover weekend mornings." - {:small "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/small.jpg", - :medium "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/med.jpg", - :large "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/large.jpg"} - {:name "Kyle's Low-Carb Grill", :categories ["Low-Carb" "Grill"], :phone "415-992-8278", :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"} - {:service "flare", :username "bob"}] - ["Pacific Heights Red White & Blue Bar & Grill is a modern and historical place to have a after-work cocktail with your pet dog." - {:small "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/small.jpg", - :medium "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/med.jpg", - :large "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "foursquare", :foursquare-photo-id "124a768c-40b4-433c-b907-4ebcb575dd51", :mayor "biggie"}] - ["Sunset American Churros is a overrated and wonderful place to take a date in July." - {:small "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/small.jpg", - :medium "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/med.jpg", - :large "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "yelp", :yelp-photo-id "ad2c2b45-b959-48cc-aac7-c049a3902c3a", :categories ["American" "Churros"]}] - ["Chinatown Paleo Food Truck is a great and modern place to have a birthday party on Thursdays." - {:small "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/small.jpg", - :medium "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/med.jpg", - :large "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/large.jpg"} - {:name "Chinatown Paleo Food Truck", :categories ["Paleo" "Food Truck"], :phone "415-583-4380", :id "aa9b5ce9-db74-470e-8573-f2faca24d546"} - {:service "yelp", :yelp-photo-id "1f41e84f-2cbb-4579-a7d4-43d4e8532353", :categories ["Paleo" "Food Truck"]}] - ["Market St. European Ice Cream Truck is a popular and historical place to pitch an investor on Thursdays." - {:small "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/small.jpg", - :medium "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/med.jpg", - :large "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/large.jpg"} - {:name "Market St. European Ice Cream Truck", :categories ["European" "Ice Cream Truck"], :phone "415-555-4197", :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"} - {:service "yelp", :yelp-photo-id "b1d569ba-ab45-4965-af9d-366dd4bc44bb", :categories ["European" "Ice Cream Truck"]}] - ["Haight Gormet Pizzeria is a well-decorated and amazing place to take visiting friends and relatives on Taco Tuesday." - {:small "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/small.jpg", - :medium "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/med.jpg", - :large "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/large.jpg"} - {:name "Haight Gormet Pizzeria", :categories ["Gormet" "Pizzeria"], :phone "415-869-2197", :id "0425bdd0-3f57-4108-80e3-78335327355a"} - {:service "twitter", :mentions ["@haight_gormet_pizzeria"], :tags ["#gormet" "#pizzeria"], :username "rasta_toucan"}] - ["SoMa TaquerÃa Diner is a well-decorated and acceptable place to have a after-work cocktail weekend evenings." - {:small "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/small.jpg", - :medium "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/med.jpg", - :large "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/large.jpg"} - {:name "SoMa TaquerÃa Diner", :categories ["TaquerÃa" "Diner"], :phone "415-947-9521", :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"} - {:service "flare", :username "bob"}] - ["Cam's Soul Food Ice Cream Truck is a acceptable and fantastic place to people-watch with your pet toucan." - {:small "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/small.jpg", - :medium "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/med.jpg", - :large "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/large.jpg"} - {:name "Cam's Soul Food Ice Cream Truck", :categories ["Soul Food" "Ice Cream Truck"], :phone "415-270-8888", :id "f474e587-1801-43ea-93d5-4c4fd96460b8"} - {:service "flare", :username "lucky_pigeon"}] - ["Tenderloin Cage-Free Sushi is a delicious and delicious place to have breakfast with your pet toucan." - {:small "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/small.jpg", - :medium "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/med.jpg", - :large "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/large.jpg"} - {:name "Tenderloin Cage-Free Sushi", :categories ["Cage-Free" "Sushi"], :phone "415-348-0644", :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"} - {:service "flare", :username "bob"}] - ["Polk St. Korean Taqueria is a horrible and atmospheric place to people-watch in July." - {:small "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/small.jpg", - :medium "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/med.jpg", - :large "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "foursquare", :foursquare-photo-id "e48f5b9f-a12e-4339-b6d7-992c44aa481b", :mayor "kyle"}] - ["Sunset Homestyle Grill is a world-famous and modern place to sip Champagne Friday nights." - {:small "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/small.jpg", - :medium "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/med.jpg", - :large "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "flare", :username "jessica"}] - ["Chinatown American Bakery is a underground and underappreciated place to have brunch in June." - {:small "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/small.jpg", - :medium "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/med.jpg", - :large "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/large.jpg"} - {:name "Chinatown American Bakery", :categories ["American" "Bakery"], :phone "415-658-7393", :id "cf55cdbd-c614-4be1-8496-0e11b195d16f"} - {:service "twitter", :mentions ["@chinatown_american_bakery"], :tags ["#american" "#bakery"], :username "amy"}] - ["Pacific Heights Red White & Blue Bar & Grill is a swell and acceptable place to take visiting friends and relatives with your pet toucan." - {:small "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/small.jpg", - :medium "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/med.jpg", - :large "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "yelp", :yelp-photo-id "6f0c3b95-145b-4b9f-8c63-a04ca70e3835", :categories ["Red White & Blue" "Bar & Grill"]}] - ["Sunset American Churros is a popular and delicious place to watch the Giants game on Taco Tuesday." - {:small "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/small.jpg", - :medium "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/med.jpg", - :large "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/large.jpg"} - {:name "Sunset American Churros", :categories ["American" "Churros"], :phone "415-191-5018", :id "2e88c921-29fb-489b-a956-d3ba1182da73"} - {:service "twitter", :mentions ["@sunset_american_churros"], :tags ["#american" "#churros"], :username "amy"}] - ["Sunset Homestyle Grill is a underappreciated and swell place to watch the Giants game on Thursdays." - {:small "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/small.jpg", - :medium "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/med.jpg", - :large "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/large.jpg"} - {:name "Sunset Homestyle Grill", :categories ["Homestyle" "Grill"], :phone "415-356-7052", :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"} - {:service "foursquare", :foursquare-photo-id "9ae208eb-c7c1-43b1-9a1e-f7a4d276e8e2", :mayor "cam_saul"}] - ["Rasta's British Food Truck is a classic and historical place to sip a glass of expensive wine Friday nights." - {:small "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/small.jpg", - :medium "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/med.jpg", - :large "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/large.jpg"} - {:name "Rasta's British Food Truck", :categories ["British" "Food Truck"], :phone "415-958-9031", :id "b6616c97-01d0-488f-a855-bcd6efe2b899"} - {:service "facebook", :facebook-photo-id "647413c2-f132-46e9-ac47-155cec8346d0", :url "http://facebook.com/photos/647413c2-f132-46e9-ac47-155cec8346d0"}] - ["Polk St. Korean Taqueria is a groovy and great place to watch the Giants game on public holidays." - {:small "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/small.jpg", - :medium "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/med.jpg", - :large "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/large.jpg"} - {:name "Polk St. Korean Taqueria", :categories ["Korean" "Taqueria"], :phone "415-511-5531", :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"} - {:service "flare", :username "jessica"}] - ["Nob Hill Gluten-Free Coffee House is a decent and acceptable place to watch the Giants game on Taco Tuesday." - {:small "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/small.jpg", - :medium "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/med.jpg", - :large "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "foursquare", :foursquare-photo-id "2d74b28c-aab1-489e-a18b-1bfb3495cc19", :mayor "sameer"}] - ["Oakland American Grill is a acceptable and popular place to conduct a business meeting on Taco Tuesday." - {:small "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/small.jpg", - :medium "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/med.jpg", - :large "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/large.jpg"} - {:name "Oakland American Grill", :categories ["American" "Grill"], :phone "415-660-0889", :id "856f907d-b669-4b9c-8337-bf9c88883746"} - {:service "foursquare", :foursquare-photo-id "18546192-88d1-4c74-a40e-b502d32f7a5b", :mayor "mandy"}] - ["Pacific Heights Soul Food Coffee House is a decent and swell place to catch a bite to eat on Saturday night." - {:small "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/small.jpg", - :medium "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/med.jpg", - :large "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/large.jpg"} - {:name "Pacific Heights Soul Food Coffee House", :categories ["Soul Food" "Coffee House"], :phone "415-838-3464", :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"} - {:service "twitter", :mentions ["@pacific_heights_soul_food_coffee_house"], :tags ["#soul" "#food" "#coffee" "#house"], :username "jane"}] - ["Polk St. Deep-Dish Hotel & Restaurant is a delicious and acceptable place to take a date on Taco Tuesday." - {:small "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/small.jpg", - :medium "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/med.jpg", - :large "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/large.jpg"} - {:name "Polk St. Deep-Dish Hotel & Restaurant", :categories ["Deep-Dish" "Hotel & Restaurant"], :phone "415-666-8681", :id "47f1698e-ae11-46f5-818b-85a59d0affba"} - {:service "foursquare", :foursquare-photo-id "2efbeca5-5a55-4310-abdd-936ae97bc1bc", :mayor "biggie"}] - ["Pacific Heights Soul Food Coffee House is a fantastic and popular place to watch the Giants game in July." - {:small "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/small.jpg", - :medium "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/med.jpg", - :large "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/large.jpg"} - {:name "Pacific Heights Soul Food Coffee House", :categories ["Soul Food" "Coffee House"], :phone "415-838-3464", :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"} - {:service "twitter", :mentions ["@pacific_heights_soul_food_coffee_house"], :tags ["#soul" "#food" "#coffee" "#house"], :username "cam_saul"}] - ["Tenderloin Gormet Restaurant is a horrible and underground place to have brunch in July." - {:small "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/small.jpg", - :medium "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/med.jpg", - :large "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/large.jpg"} - {:name "Tenderloin Gormet Restaurant", :categories ["Gormet" "Restaurant"], :phone "415-127-4197", :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"} - {:service "yelp", :yelp-photo-id "86617b70-de39-40aa-9ca2-9c33c5bd1fa4", :categories ["Gormet" "Restaurant"]}] - ["Pacific Heights Red White & Blue Bar & Grill is a underappreciated and modern place to meet new friends with friends." - {:small "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/small.jpg", - :medium "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/med.jpg", - :large "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/large.jpg"} - {:name "Pacific Heights Red White & Blue Bar & Grill", :categories ["Red White & Blue" "Bar & Grill"], :phone "415-208-2550", :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"} - {:service "twitter", :mentions ["@pacific_heights_red_white_&_blue_bar_&_grill"], :tags ["#red" "#white" "#&" "#blue" "#bar" "#&" "#grill"], :username "mandy"}] - ["Nob Hill Gluten-Free Coffee House is a underappreciated and swell place to take visiting friends and relatives with your pet dog." - {:small "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/small.jpg", - :medium "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/med.jpg", - :large "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/large.jpg"} - {:name "Nob Hill Gluten-Free Coffee House", :categories ["Gluten-Free" "Coffee House"], :phone "415-605-9554", :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"} - {:service "flare", :username "jane"}] - ["Mission Japanese Coffee House is a modern and historical place to take visiting friends and relatives on Taco Tuesday." - {:small "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/small.jpg", - :medium "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/med.jpg", - :large "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/large.jpg"} - {:name "Mission Japanese Coffee House", :categories ["Japanese" "Coffee House"], :phone "415-561-0506", :id "60dd274e-0cbf-4521-946d-8a4e0f151150"} - {:service "facebook", :facebook-photo-id "83699a56-416c-49cc-a31a-dd6b4c5aa91b", :url "http://facebook.com/photos/83699a56-416c-49cc-a31a-dd6b4c5aa91b"}] - ["Oakland Low-Carb Bakery is a modern and fantastic place to conduct a business meeting on a Tuesday afternoon." - {:small "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/small.jpg", - :medium "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/med.jpg", - :large "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/large.jpg"} - {:name "Oakland Low-Carb Bakery", :categories ["Low-Carb" "Bakery"], :phone "415-546-0101", :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"} - {:service "twitter", :mentions ["@oakland_low_carb_bakery"], :tags ["#low-carb" "#bakery"], :username "bob"}] - ["Cam's Old-Fashioned Coffee House is a world-famous and decent place to sip a glass of expensive wine on a Tuesday afternoon." - {:small "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/small.jpg", - :medium "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/med.jpg", - :large "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/large.jpg"} - {:name "Cam's Old-Fashioned Coffee House", :categories ["Old-Fashioned" "Coffee House"], :phone "415-868-2973", :id "27592c2b-e682-44bb-be28-8e9a622becca"} - {:service "yelp", :yelp-photo-id "de90a66b-d19e-4665-b0c2-c64ba899a02b", :categories ["Old-Fashioned" "Coffee House"]}] - ["Rasta's Mexican Sushi is a fantastic and atmospheric place to have a after-work cocktail weekday afternoons." - {:small "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/small.jpg", - :medium "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/med.jpg", - :large "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/large.jpg"} - {:name "Rasta's Mexican Sushi", :categories ["Mexican" "Sushi"], :phone "415-387-1284", :id "e4912a22-e6ac-4806-8377-6497bf533a21"} - {:service "flare", :username "amy"}] - ["SoMa Japanese Churros is a underappreciated and swell place to take a date Friday nights." - {:small "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/small.jpg", - :medium "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/med.jpg", - :large "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/large.jpg"} - {:name "SoMa Japanese Churros", :categories ["Japanese" "Churros"], :phone "415-404-1510", :id "373858b2-e634-45d0-973d-4d0fed8c438b"} - {:service "facebook", :facebook-photo-id "bdd68c35-bffc-4091-b32c-bf8bf3a8f834", :url "http://facebook.com/photos/bdd68c35-bffc-4091-b32c-bf8bf3a8f834"}] - ["Lucky's Cage-Free Liquor Store is a modern and groovy place to take a date with friends." - {:small "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/small.jpg", - :medium "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/med.jpg", - :large "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/large.jpg"} - {:name "Lucky's Cage-Free Liquor Store", :categories ["Cage-Free" "Liquor Store"], :phone "415-341-3219", :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"} - {:service "flare", :username "lucky_pigeon"}]]]] + [[{:service "facebook", + :facebook-photo-id "e46749c3-c532-4dc7-bdc3-da274de3c3ab", + :url + "http://facebook.com/photos/e46749c3-c532-4dc7-bdc3-da274de3c3ab"} + "Lucky's Gluten-Free Café is a atmospheric and delicious place to have a drink during winter." + {:small + "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/small.jpg", + :medium + "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/med.jpg", + :large + "http://cloudfront.net/50576ac9-2211-4198-8915-265d32a6ba82/large.jpg"} + {:name "Lucky's Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-740-2328", + :id "379af987-ad40-4a93-88a6-0233e1c14649"}] + [{:service "flare", :username "mandy"} + "Joe's Homestyle Eatery is a exclusive and historical place to have breakfast on public holidays." + {:small + "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/small.jpg", + :medium + "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/med.jpg", + :large + "http://cloudfront.net/b90e3288-c02c-4744-823a-d4110ca2d71b/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "flare", :username "mandy"} + "Lower Pac Heights Cage-Free Coffee House is a underground and fantastic place to catch a bite to eat on Saturday night." + {:small + "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/small.jpg", + :medium + "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/med.jpg", + :large + "http://cloudfront.net/2122308c-e15a-460e-98a0-a3b604db3cd1/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "facebook", + :facebook-photo-id "a9855e18-8612-4a97-b86a-aafe0d747bb4", + :url + "http://facebook.com/photos/a9855e18-8612-4a97-b86a-aafe0d747bb4"} + "Oakland European Liquor Store is a swell and wonderful place to take a date in July." + {:small + "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/small.jpg", + :medium + "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/med.jpg", + :large + "http://cloudfront.net/2fb9d1ae-a3d1-4b27-966e-260983f01813/large.jpg"} + {:name "Oakland European Liquor Store", + :categories ["European" "Liquor Store"], + :phone "415-559-1516", + :id "e342e7b7-e82d-475d-a822-b2df9c84850d"}] + [{:service "twitter", + :mentions ["@tenderloin_gormet_restaurant"], + :tags ["#gormet" "#restaurant"], + :username "tupac"} + "Tenderloin Gormet Restaurant is a world-famous and popular place to have a drink during winter." + {:small + "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/small.jpg", + :medium + "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/med.jpg", + :large + "http://cloudfront.net/30ef9979-b1a9-40a9-82a4-32565401bab5/large.jpg"} + {:name "Tenderloin Gormet Restaurant", + :categories ["Gormet" "Restaurant"], + :phone "415-127-4197", + :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"}] + [{:service "facebook", + :facebook-photo-id "58084e61-6381-4313-83be-6e35c8424600", + :url + "http://facebook.com/photos/58084e61-6381-4313-83be-6e35c8424600"} + "Marina Modern Sushi is a fantastic and underappreciated place to have a birthday party weekday afternoons." + {:small + "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/small.jpg", + :medium + "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/med.jpg", + :large + "http://cloudfront.net/bc7bac7f-5e92-4023-9b35-fd80f106274a/large.jpg"} + {:name "Marina Modern Sushi", + :categories ["Modern" "Sushi"], + :phone "415-393-7672", + :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"}] + [{:service "yelp", + :yelp-photo-id "976fdffd-70ca-4b57-b214-80f0eb80f619", + :categories ["Homestyle" "Grill"]} + "Sunset Homestyle Grill is a world-famous and well-decorated place to conduct a business meeting Friday nights." + {:small + "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/small.jpg", + :medium + "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/med.jpg", + :large + "http://cloudfront.net/3fc720ca-07d7-48b7-bcab-550d951f58b9/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "yelp", + :yelp-photo-id "058b7d4e-b92b-4408-ba67-659f5b640e4b", + :categories ["Low-Carb" "Grill"]} + "Kyle's Low-Carb Grill is a delicious and underappreciated place to have a after-work cocktail after baseball games." + {:small + "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/small.jpg", + :medium + "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/med.jpg", + :large + "http://cloudfront.net/49b48260-5c56-4873-956e-c13423f4feed/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "facebook", + :facebook-photo-id "b2a4862c-c5b7-4cfb-8ff6-fb564c28f4c8", + :url + "http://facebook.com/photos/b2a4862c-c5b7-4cfb-8ff6-fb564c28f4c8"} + "Mission Homestyle Churros is a groovy and wonderful place to catch a bite to eat weekday afternoons." + {:small + "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/small.jpg", + :medium + "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/med.jpg", + :large + "http://cloudfront.net/2110742e-7e30-4a0d-8df4-1d20d2503f42/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "twitter", + :mentions ["@sameers_pizza_liquor_store"], + :tags ["#pizza" "#liquor" "#store"], + :username "cam_saul"} + "Sameer's Pizza Liquor Store is a overrated and decent place to sip a glass of expensive wine during summer." + {:small + "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/small.jpg", + :medium + "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/med.jpg", + :large + "http://cloudfront.net/df8c679d-93d5-4008-a129-b33711892cba/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "facebook", + :facebook-photo-id "d3717643-c5c3-4e36-8721-99c2fd65972f", + :url + "http://facebook.com/photos/d3717643-c5c3-4e36-8721-99c2fd65972f"} + "Market St. European Ice Cream Truck is a overrated and overrated place to watch the Giants game on Saturday night." + {:small + "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/small.jpg", + :medium + "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/med.jpg", + :large + "http://cloudfront.net/25be5a46-88b0-4ac7-965d-196d4dbab4b8/large.jpg"} + {:name "Market St. European Ice Cream Truck", + :categories ["European" "Ice Cream Truck"], + :phone "415-555-4197", + :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"}] + [{:service "foursquare", + :foursquare-photo-id "5eac8186-24ef-4369-9b46-3bbec215a9c3", + :mayor "cam_saul"} + "Haight Mexican Restaurant is a fantastic and overrated place to sip a glass of expensive wine with your pet toucan." + {:small + "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/small.jpg", + :medium + "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/med.jpg", + :large + "http://cloudfront.net/d01af223-d655-4b62-83a9-63a03118b288/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "flare", :username "amy"} + "Rasta's Mexican Sushi is a overrated and well-decorated place to watch the Warriors game with your pet toucan." + {:small + "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/small.jpg", + :medium + "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/med.jpg", + :large + "http://cloudfront.net/c807068e-1def-4902-987a-75e4e06636f9/large.jpg"} + {:name "Rasta's Mexican Sushi", + :categories ["Mexican" "Sushi"], + :phone "415-387-1284", + :id "e4912a22-e6ac-4806-8377-6497bf533a21"}] + [{:service "facebook", + :facebook-photo-id "d7ab7046-43cd-472e-a48d-1b11cd3f7b55", + :url + "http://facebook.com/photos/d7ab7046-43cd-472e-a48d-1b11cd3f7b55"} + "Market St. European Ice Cream Truck is a amazing and groovy place to have a drink on Saturday night." + {:small + "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/small.jpg", + :medium + "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/med.jpg", + :large + "http://cloudfront.net/1b91096b-206b-4823-8ef9-13f0bf9ac76e/large.jpg"} + {:name "Market St. European Ice Cream Truck", + :categories ["European" "Ice Cream Truck"], + :phone "415-555-4197", + :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"}] + [{:service "flare", :username "lucky_pigeon"} + "Tenderloin Paleo Hotel & Restaurant is a underappreciated and fantastic place to take visiting friends and relatives after baseball games." + {:small + "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/small.jpg", + :medium + "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/med.jpg", + :large + "http://cloudfront.net/5ad20c96-0a12-43ff-97e9-8b8b7e373b6c/large.jpg"} + {:name "Tenderloin Paleo Hotel & Restaurant", + :categories ["Paleo" "Hotel & Restaurant"], + :phone "415-402-1652", + :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"}] + [{:service "foursquare", + :foursquare-photo-id "b8ef27c8-8dc4-45ff-9485-c130889eaea1", + :mayor "joe"} + "Lower Pac Heights Cage-Free Coffee House is a well-decorated and amazing place to watch the Warriors game when hungover." + {:small + "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/small.jpg", + :medium + "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/med.jpg", + :large + "http://cloudfront.net/5809d5db-cfc0-4f54-8760-228bdb3e1697/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "facebook", + :facebook-photo-id "3c80ed1f-409b-4cfa-bf78-9bbc8cd8f9f5", + :url + "http://facebook.com/photos/3c80ed1f-409b-4cfa-bf78-9bbc8cd8f9f5"} + "Polk St. Mexican Coffee House is a world-famous and modern place to pitch an investor in the spring." + {:small + "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/small.jpg", + :medium + "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/med.jpg", + :large + "http://cloudfront.net/a351a826-49e8-43d5-b983-7b4f013e872e/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "yelp", + :yelp-photo-id "4ab42195-c3df-49e2-b8e9-20e4f5fae63b", + :categories ["Homestyle" "Grill"]} + "Sunset Homestyle Grill is a amazing and world-famous place to drink a craft beer when hungover." + {:small + "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/small.jpg", + :medium + "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/med.jpg", + :large + "http://cloudfront.net/d6ff64d9-1779-4cbf-bbbf-6f666a67691e/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "facebook", + :facebook-photo-id "0a3e4b0e-ecd7-4c55-8d5f-2265ad57b02a", + :url + "http://facebook.com/photos/0a3e4b0e-ecd7-4c55-8d5f-2265ad57b02a"} + "Marina Modern Bar & Grill is a atmospheric and great place to have breakfast during winter." + {:small + "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/small.jpg", + :medium + "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/med.jpg", + :large + "http://cloudfront.net/95842ec7-663b-48db-b667-d6b70d0c194d/large.jpg"} + {:name "Marina Modern Bar & Grill", + :categories ["Modern" "Bar & Grill"], + :phone "415-203-8530", + :id "806144f1-bb7a-4271-8fcb-fc6550f51676"}] + [{:service "twitter", + :mentions ["@polk_st._mexican_coffee_house"], + :tags ["#mexican" "#coffee" "#house"], + :username "amy"} + "Polk St. Mexican Coffee House is a swell and wonderful place to have a after-work cocktail in June." + {:small + "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/small.jpg", + :medium + "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/med.jpg", + :large + "http://cloudfront.net/4b773ee9-a6db-4e6a-8314-24530a35cdf5/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "yelp", + :yelp-photo-id "b0b7027e-5884-48fb-93d4-d11f44c564df", + :categories ["Korean" "Taqueria"]} + "Nob Hill Korean Taqueria is a decent and modern place to catch a bite to eat on Taco Tuesday." + {:small + "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/small.jpg", + :medium + "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/med.jpg", + :large + "http://cloudfront.net/e87adb96-9303-4728-a2e2-ce4d2c1edb74/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "foursquare", + :foursquare-photo-id "217e84df-2032-4e13-b32d-20745fc36be0", + :mayor "amy"} + "SF Deep-Dish Eatery is a world-famous and historical place to catch a bite to eat on a Tuesday afternoon." + {:small + "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/small.jpg", + :medium + "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/med.jpg", + :large + "http://cloudfront.net/bda4e656-d27d-44f2-a0ca-8aa8856a4eb5/large.jpg"} + {:name "SF Deep-Dish Eatery", + :categories ["Deep-Dish" "Eatery"], + :phone "415-476-9257", + :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"}] + [{:service "twitter", + :mentions ["@sunset_deep_dish_hotel_&_restaurant"], + :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], + :username "lucky_pigeon"} + "Sunset Deep-Dish Hotel & Restaurant is a popular and groovy place to have a drink weekend mornings." + {:small + "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/small.jpg", + :medium + "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/med.jpg", + :large + "http://cloudfront.net/6f15c573-5718-4e17-a64b-5b5ac3a4be00/large.jpg"} + {:name "Sunset Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-332-0978", + :id "a80745c7-af74-4579-8932-70dd488269e6"}] + [{:service "foursquare", + :foursquare-photo-id "0928a8d9-8198-4004-ae00-58025bc98a4b", + :mayor "mandy"} + "Polk St. Japanese Liquor Store is a fantastic and delicious place to catch a bite to eat with your pet dog." + {:small + "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/small.jpg", + :medium + "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/med.jpg", + :large + "http://cloudfront.net/5af61fa2-9031-4be5-86c8-210e9612a184/large.jpg"} + {:name "Polk St. Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-726-7986", + :id "b57ceac5-328d-4b65-9909-a1f9abc93015"}] + [{:service "facebook", + :facebook-photo-id "533e16df-df5c-4368-81fe-ca4c53797f4c", + :url + "http://facebook.com/photos/533e16df-df5c-4368-81fe-ca4c53797f4c"} + "Chinatown Paleo Food Truck is a modern and underappreciated place to nurse a hangover weekend mornings." + {:small + "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/small.jpg", + :medium + "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/med.jpg", + :large + "http://cloudfront.net/55ab1c20-acae-491b-afa2-455e35c924bd/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "facebook", + :facebook-photo-id "71b1c7b4-cf56-4cb1-985a-d97edb8bda7c", + :url + "http://facebook.com/photos/71b1c7b4-cf56-4cb1-985a-d97edb8bda7c"} + "Rasta's Paleo Churros is a well-decorated and underground place to sip a glass of expensive wine in the fall." + {:small + "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/small.jpg", + :medium + "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/med.jpg", + :large + "http://cloudfront.net/45c98735-bbc7-4d81-bac6-d45527446f71/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "facebook", + :facebook-photo-id "250305eb-9827-43b8-a190-401a7170eb1e", + :url + "http://facebook.com/photos/250305eb-9827-43b8-a190-401a7170eb1e"} + "Market St. Gluten-Free Café is a classic and exclusive place to drink a craft beer with friends." + {:small + "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/small.jpg", + :medium + "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/med.jpg", + :large + "http://cloudfront.net/569435ff-07e5-4369-bf69-075ee03b3f74/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "foursquare", + :foursquare-photo-id "c1759648-8365-48ca-8068-f3ba5fbb32a4", + :mayor "mandy"} + "Lucky's Old-Fashioned Eatery is a underground and delicious place to watch the Warriors game during summer." + {:small + "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/small.jpg", + :medium + "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/med.jpg", + :large + "http://cloudfront.net/188511ac-9217-42ca-8d1e-973072669935/large.jpg"} + {:name "Lucky's Old-Fashioned Eatery", + :categories ["Old-Fashioned" "Eatery"], + :phone "415-362-2338", + :id "71dc221c-6e82-4d06-8709-93293121b1da"}] + [{:service "facebook", + :facebook-photo-id "3f7b182d-22b8-401c-939d-bd08cde5a9ac", + :url + "http://facebook.com/photos/3f7b182d-22b8-401c-939d-bd08cde5a9ac"} + "Polk St. Japanese Liquor Store is a underappreciated and modern place to catch a bite to eat weekend mornings." + {:small + "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/small.jpg", + :medium + "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/med.jpg", + :large + "http://cloudfront.net/f143dd2d-b46d-4444-ac48-f5253fa0fdae/large.jpg"} + {:name "Polk St. Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-726-7986", + :id "b57ceac5-328d-4b65-9909-a1f9abc93015"}] + [{:service "foursquare", + :foursquare-photo-id "6a0d547d-2053-4c14-b35f-f658cfc21f84", + :mayor "sameer"} + "Cam's Mexican Gastro Pub is a great and world-famous place to catch a bite to eat when hungover." + {:small + "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/small.jpg", + :medium + "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/med.jpg", + :large + "http://cloudfront.net/dc69ae89-81f7-49d3-aa4d-ce48621f7497/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "facebook", + :facebook-photo-id "c5fff3c3-8ea4-42d2-b3dd-29d83c6a9ed2", + :url + "http://facebook.com/photos/c5fff3c3-8ea4-42d2-b3dd-29d83c6a9ed2"} + "Lucky's Gluten-Free Gastro Pub is a wonderful and horrible place to have a birthday party with friends." + {:small + "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/small.jpg", + :medium + "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/med.jpg", + :large + "http://cloudfront.net/5c9aedc4-916f-4552-b520-084495bf5cce/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "foursquare", + :foursquare-photo-id "eff5660c-a3bf-42e5-9406-a9181f3abf41", + :mayor "lucky_pigeon"} + "Kyle's Chinese Restaurant is a classic and decent place to catch a bite to eat the first Sunday of the month." + {:small + "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/small.jpg", + :medium + "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/med.jpg", + :large + "http://cloudfront.net/460f06c2-3fd1-4356-a2ed-cddfef334a76/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "foursquare", + :foursquare-photo-id "901ae47c-e6f2-4511-a20b-b89e2b600239", + :mayor "cam_saul"} + "Nob Hill Gluten-Free Coffee House is a wonderful and overrated place to meet new friends the second Saturday of the month." + {:small + "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/small.jpg", + :medium + "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/med.jpg", + :large + "http://cloudfront.net/3e975ead-6942-48d4-98a4-01147baf0e9c/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "facebook", + :facebook-photo-id "b4f4f18b-fb73-42a5-9628-607f6526a8ca", + :url + "http://facebook.com/photos/b4f4f18b-fb73-42a5-9628-607f6526a8ca"} + "Sunset American Churros is a amazing and exclusive place to have a birthday party the second Saturday of the month." + {:small + "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/small.jpg", + :medium + "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/med.jpg", + :large + "http://cloudfront.net/7db04c5a-49bd-4606-b3c6-af074075db64/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "flare", :username "jessica"} + "SoMa British Bakery is a atmospheric and underground place to sip Champagne in July." + {:small + "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/small.jpg", + :medium + "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/med.jpg", + :large + "http://cloudfront.net/e3da9d78-f911-451c-a64c-929ecbfb477d/large.jpg"} + {:name "SoMa British Bakery", + :categories ["British" "Bakery"], + :phone "415-909-5728", + :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"}] + [{:service "twitter", + :mentions ["@tenderloin_cage_free_sushi"], + :tags ["#cage-free" "#sushi"], + :username "joe"} + "Tenderloin Cage-Free Sushi is a swell and historical place to people-watch on Thursdays." + {:small + "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/small.jpg", + :medium + "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/med.jpg", + :large + "http://cloudfront.net/7ea17999-9e65-414a-a86c-891cb0ee41bc/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "yelp", + :yelp-photo-id "6e4d9aff-63d3-4b00-9134-a4a3727b9d8d", + :categories ["American" "Churros"]} + "Sunset American Churros is a great and atmospheric place to conduct a business meeting with friends." + {:small + "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/small.jpg", + :medium + "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/med.jpg", + :large + "http://cloudfront.net/e41c5cf4-8ccd-410e-bbad-feed53827baf/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "facebook", + :facebook-photo-id "ff353525-8a3c-4510-85ec-c22d5fe5b831", + :url + "http://facebook.com/photos/ff353525-8a3c-4510-85ec-c22d5fe5b831"} + "Rasta's British Food Truck is a fantastic and underground place to sip a glass of expensive wine in June." + {:small + "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/small.jpg", + :medium + "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/med.jpg", + :large + "http://cloudfront.net/6670e042-020e-4919-90da-020cf93fab95/large.jpg"} + {:name "Rasta's British Food Truck", + :categories ["British" "Food Truck"], + :phone "415-958-9031", + :id "b6616c97-01d0-488f-a855-bcd6efe2b899"}] + [{:service "facebook", + :facebook-photo-id "6387d17f-d000-488b-b9f4-5f808e517f28", + :url + "http://facebook.com/photos/6387d17f-d000-488b-b9f4-5f808e517f28"} + "Haight Gormet Pizzeria is a swell and modern place to have a after-work cocktail weekday afternoons." + {:small + "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/small.jpg", + :medium + "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/med.jpg", + :large + "http://cloudfront.net/0d491f2a-db93-4226-bbee-8e3647a6d3fa/large.jpg"} + {:name "Haight Gormet Pizzeria", + :categories ["Gormet" "Pizzeria"], + :phone "415-869-2197", + :id "0425bdd0-3f57-4108-80e3-78335327355a"}] + [{:service "facebook", + :facebook-photo-id "8c4a5b4b-f72b-41bc-8242-3d31df7f175a", + :url + "http://facebook.com/photos/8c4a5b4b-f72b-41bc-8242-3d31df7f175a"} + "Haight Chinese Gastro Pub is a modern and great place to meet new friends the second Saturday of the month." + {:small + "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/small.jpg", + :medium + "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/med.jpg", + :large + "http://cloudfront.net/562d5a4b-6d03-43b4-89f6-68270448c8cc/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "yelp", + :yelp-photo-id "0562bf77-bb42-464e-951f-6e18206de08b", + :categories ["Old-Fashioned" "Coffee House"]} + "Cam's Old-Fashioned Coffee House is a fantastic and overrated place to have brunch in the spring." + {:small + "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/small.jpg", + :medium + "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/med.jpg", + :large + "http://cloudfront.net/06e5458e-25da-45b6-bb26-7662ac033edc/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "foursquare", + :foursquare-photo-id "506df972-9899-40d8-be2d-052a0d32730b", + :mayor "joe"} + "SoMa Old-Fashioned Pizzeria is a underappreciated and wonderful place to have a after-work cocktail on a Tuesday afternoon." + {:small + "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/small.jpg", + :medium + "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/med.jpg", + :large + "http://cloudfront.net/c727c895-8572-453b-b8bf-921298fb9240/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@sameers_pizza_liquor_store"], + :tags ["#pizza" "#liquor" "#store"], + :username "sameer"} + "Sameer's Pizza Liquor Store is a classic and decent place to have a birthday party with your pet dog." + {:small + "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/small.jpg", + :medium + "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/med.jpg", + :large + "http://cloudfront.net/4c023049-681d-4c45-a1ed-b7859bb9b8aa/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "facebook", + :facebook-photo-id "d4b18407-5358-43a0-8bee-c53606ceb4b4", + :url + "http://facebook.com/photos/d4b18407-5358-43a0-8bee-c53606ceb4b4"} + "Oakland Afgan Coffee House is a wonderful and historical place to watch the Warriors game Friday nights." + {:small + "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/small.jpg", + :medium + "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/med.jpg", + :large + "http://cloudfront.net/8f18bcd6-66de-414a-a087-5d57f042b26b/large.jpg"} + {:name "Oakland Afgan Coffee House", + :categories ["Afgan" "Coffee House"], + :phone "415-674-0208", + :id "dcc9efd9-f34c-4ca1-9a41-386f1130f411"}] + [{:service "yelp", + :yelp-photo-id "9d710fa3-1505-43a4-8f41-ea8c188ee172", + :categories ["Homestyle" "Churros"]} + "Mission Homestyle Churros is a swell and well-decorated place to sip Champagne with your pet dog." + {:small + "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/small.jpg", + :medium + "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/med.jpg", + :large + "http://cloudfront.net/58de8593-50a8-494f-9aa9-99f64db4339b/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "facebook", + :facebook-photo-id "81f9c252-5b76-45f1-baf9-a2d919ee7695", + :url + "http://facebook.com/photos/81f9c252-5b76-45f1-baf9-a2d919ee7695"} + "Haight Soul Food Hotel & Restaurant is a swell and swell place to have a drink in the spring." + {:small + "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/small.jpg", + :medium + "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/med.jpg", + :large + "http://cloudfront.net/f188d9d9-211f-4b66-87bd-6271f05fbcc6/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "flare", :username "kyle"} + "Marina Japanese Liquor Store is a historical and horrible place to drink a craft beer the second Saturday of the month." + {:small + "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/small.jpg", + :medium + "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/med.jpg", + :large + "http://cloudfront.net/4a27d895-0832-47c1-8e30-5b41c7f2d8aa/large.jpg"} + {:name "Marina Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-587-9819", + :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"}] + [{:service "facebook", + :facebook-photo-id "adf58b74-9ac3-4c27-a4a7-b951736b79ae", + :url + "http://facebook.com/photos/adf58b74-9ac3-4c27-a4a7-b951736b79ae"} + "SF British Pop-Up Food Stand is a groovy and popular place to meet new friends weekend evenings." + {:small + "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/small.jpg", + :medium + "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/med.jpg", + :large + "http://cloudfront.net/b1397f37-124b-435d-ab72-ac9002e56f7b/large.jpg"} + {:name "SF British Pop-Up Food Stand", + :categories ["British" "Pop-Up Food Stand"], + :phone "415-441-3725", + :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"}] + [{:service "twitter", + :mentions ["@sameers_gmo_free_restaurant"], + :tags ["#gmo-free" "#restaurant"], + :username "cam_saul"} + "Sameer's GMO-Free Restaurant is a delicious and fantastic place to have breakfast in the spring." + {:small + "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/small.jpg", + :medium + "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/med.jpg", + :large + "http://cloudfront.net/e4251131-92d3-4097-82f5-782405eb0ae5/large.jpg"} + {:name "Sameer's GMO-Free Restaurant", + :categories ["GMO-Free" "Restaurant"], + :phone "415-128-9430", + :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"}] + [{:service "flare", :username "tupac"} + "Rasta's Paleo Churros is a underappreciated and atmospheric place to catch a bite to eat when hungover." + {:small + "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/small.jpg", + :medium + "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/med.jpg", + :large + "http://cloudfront.net/1729a6bc-19f5-4084-b4fe-e91eafbe07cb/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "facebook", + :facebook-photo-id "98c776ab-0c90-4fa4-a4c6-6f0e19ad6b9e", + :url + "http://facebook.com/photos/98c776ab-0c90-4fa4-a4c6-6f0e19ad6b9e"} + "SoMa Old-Fashioned Pizzeria is a underground and fantastic place to drink a craft beer when hungover." + {:small + "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/small.jpg", + :medium + "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/med.jpg", + :large + "http://cloudfront.net/7905ee6d-f63b-4f36-bc51-c8006b4c39f4/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@soma_old_fashioned_pizzeria"], + :tags ["#old-fashioned" "#pizzeria"], + :username "bob"} + "SoMa Old-Fashioned Pizzeria is a exclusive and underappreciated place to pitch an investor the first Sunday of the month." + {:small + "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/small.jpg", + :medium + "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/med.jpg", + :large + "http://cloudfront.net/f46c53c3-8987-4dee-88ba-6693f884f53a/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@oakland_american_grill"], + :tags ["#american" "#grill"], + :username "cam_saul"} + "Oakland American Grill is a groovy and modern place to sip a glass of expensive wine weekend mornings." + {:small + "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/small.jpg", + :medium + "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/med.jpg", + :large + "http://cloudfront.net/700e2004-8a15-4cd6-9341-413c09249098/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "twitter", + :mentions ["@polk_st._deep_dish_hotel_&_restaurant"], + :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], + :username "mandy"} + "Polk St. Deep-Dish Hotel & Restaurant is a atmospheric and horrible place to sip a glass of expensive wine weekday afternoons." + {:small + "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/small.jpg", + :medium + "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/med.jpg", + :large + "http://cloudfront.net/d60aea9e-0e15-4bf5-90a8-68fcbb7f1f06/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "twitter", + :mentions ["@haight_chinese_gastro_pub"], + :tags ["#chinese" "#gastro" "#pub"], + :username "cam_saul"} + "Haight Chinese Gastro Pub is a world-famous and amazing place to sip Champagne when hungover." + {:small + "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/small.jpg", + :medium + "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/med.jpg", + :large + "http://cloudfront.net/cae5889b-70e1-4f7e-b097-709ae2db369e/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "foursquare", + :foursquare-photo-id "9543ebb3-8d33-468f-8ba1-980fe43aa09b", + :mayor "amy"} + "Lucky's Gluten-Free Gastro Pub is a popular and overrated place to watch the Giants game the second Saturday of the month." + {:small + "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/small.jpg", + :medium + "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/med.jpg", + :large + "http://cloudfront.net/1f98a61a-b644-4adf-bb2a-24207ed563f8/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "twitter", + :mentions ["@luckys_deep_dish_gastro_pub"], + :tags ["#deep-dish" "#gastro" "#pub"], + :username "biggie"} + "Lucky's Deep-Dish Gastro Pub is a great and family-friendly place to meet new friends on Saturday night." + {:small + "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/small.jpg", + :medium + "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/med.jpg", + :large + "http://cloudfront.net/58db9f8b-70b5-4f9e-9b3e-9eab40907b1e/large.jpg"} + {:name "Lucky's Deep-Dish Gastro Pub", + :categories ["Deep-Dish" "Gastro Pub"], + :phone "415-487-4085", + :id "0136c454-0968-41cd-a237-ceec5724cab8"}] + [{:service "flare", :username "mandy"} + "Alcatraz Pizza Churros is a delicious and classic place to have breakfast on Taco Tuesday." + {:small + "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/small.jpg", + :medium + "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/med.jpg", + :large + "http://cloudfront.net/d3e4995f-57ad-4b71-8321-6d71082cb24c/large.jpg"} + {:name "Alcatraz Pizza Churros", + :categories ["Pizza" "Churros"], + :phone "415-754-7867", + :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"}] + [{:service "flare", :username "jane"} + "SoMa Old-Fashioned Pizzeria is a popular and amazing place to take visiting friends and relatives during summer." + {:small + "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/small.jpg", + :medium + "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/med.jpg", + :large + "http://cloudfront.net/d516bb79-8683-4e5a-a2e0-937144346293/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "yelp", + :yelp-photo-id "cf77c28d-03a5-44d4-9882-4fe4bef60a02", + :categories ["Old-Fashioned" "Pizzeria"]} + "SoMa Old-Fashioned Pizzeria is a underappreciated and underappreciated place to people-watch weekend evenings." + {:small + "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/small.jpg", + :medium + "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/med.jpg", + :large + "http://cloudfront.net/0c331686-89ff-451e-8283-d08742746c3b/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@tenderloin_cage_free_sushi"], + :tags ["#cage-free" "#sushi"], + :username "mandy"} + "Tenderloin Cage-Free Sushi is a overrated and historical place to catch a bite to eat the first Sunday of the month." + {:small + "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/small.jpg", + :medium + "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/med.jpg", + :large + "http://cloudfront.net/1b419482-e80c-4783-8b7f-d21b04e35a4b/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "yelp", + :yelp-photo-id "d84760ec-d55c-486d-9b53-e43a20e0395c", + :categories ["Red White & Blue" "Café"]} + "Polk St. Red White & Blue Café is a historical and swell place to have a birthday party in the fall." + {:small + "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/small.jpg", + :medium + "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/med.jpg", + :large + "http://cloudfront.net/8b0d7fc8-2b5c-4bfb-a947-3fd4753b118c/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "foursquare", + :foursquare-photo-id "64feb4b2-a403-4be7-aa96-5c5e5595eba5", + :mayor "joe"} + "Market St. Homestyle Pop-Up Food Stand is a classic and underground place to take a date during winter." + {:small + "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/small.jpg", + :medium + "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/med.jpg", + :large + "http://cloudfront.net/96e29fb5-834c-437c-9747-b2f33a3f096c/large.jpg"} + {:name "Market St. Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-213-3030", + :id "2d873280-e43d-449e-9940-af96ae7df718"}] + [{:service "twitter", + :mentions ["@marina_low_carb_food_truck"], + :tags ["#low-carb" "#food" "#truck"], + :username "cam_saul"} + "Marina Low-Carb Food Truck is a classic and groovy place to nurse a hangover weekend evenings." + {:small + "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/small.jpg", + :medium + "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/med.jpg", + :large + "http://cloudfront.net/08213fe2-157e-46f7-a71d-82588347d023/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "foursquare", + :foursquare-photo-id "b53c2c6c-b767-4816-b881-52613ecb438d", + :mayor "rasta_toucan"} + "Marina Modern Bar & Grill is a modern and well-decorated place to watch the Giants game on public holidays." + {:small + "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/small.jpg", + :medium + "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/med.jpg", + :large + "http://cloudfront.net/00be0fe9-6765-4c76-98ee-f7e2e7e0b7b9/large.jpg"} + {:name "Marina Modern Bar & Grill", + :categories ["Modern" "Bar & Grill"], + :phone "415-203-8530", + :id "806144f1-bb7a-4271-8fcb-fc6550f51676"}] + [{:service "foursquare", + :foursquare-photo-id "a40cb559-779a-45cb-82d4-82361f7ac9c1", + :mayor "jane"} + "Alcatraz Cage-Free Restaurant is a swell and underappreciated place to have a after-work cocktail in the fall." + {:small + "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/small.jpg", + :medium + "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/med.jpg", + :large + "http://cloudfront.net/3e6adc7d-41cc-426d-8221-e1d75e60c6c9/large.jpg"} + {:name "Alcatraz Cage-Free Restaurant", + :categories ["Cage-Free" "Restaurant"], + :phone "415-568-0312", + :id "fe0c7f8e-4937-4a76-bda4-44ad89c5231c"}] + [{:service "flare", :username "amy"} + "Kyle's Low-Carb Grill is a exclusive and fantastic place to have a birthday party on Saturday night." + {:small + "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/small.jpg", + :medium + "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/med.jpg", + :large + "http://cloudfront.net/ec5fd5d5-4e24-4a8e-99a8-1c06c9b6ad83/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "foursquare", + :foursquare-photo-id "4f9300e3-8787-4429-9837-52a8949a920e", + :mayor "bob"} + "Marina Modern Sushi is a exclusive and fantastic place to conduct a business meeting in the fall." + {:small + "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/small.jpg", + :medium + "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/med.jpg", + :large + "http://cloudfront.net/a44ff874-27fd-480a-b723-79516d9a0f5a/large.jpg"} + {:name "Marina Modern Sushi", + :categories ["Modern" "Sushi"], + :phone "415-393-7672", + :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"}] + [{:service "flare", :username "kyle"} + "Haight Soul Food Hotel & Restaurant is a world-famous and popular place to nurse a hangover Friday nights." + {:small + "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/small.jpg", + :medium + "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/med.jpg", + :large + "http://cloudfront.net/c9d748f5-245c-4a0f-9308-76e56ae5666c/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "facebook", + :facebook-photo-id "0bbffb73-91f1-4a27-97df-5e0dbae6532b", + :url + "http://facebook.com/photos/0bbffb73-91f1-4a27-97df-5e0dbae6532b"} + "Sunset American Churros is a decent and fantastic place to have a drink weekend mornings." + {:small + "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/small.jpg", + :medium + "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/med.jpg", + :large + "http://cloudfront.net/7a5085d9-5fe6-49c1-9556-9f196f0938ae/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "yelp", + :yelp-photo-id "fd35ac16-71ef-48f8-8512-2078bda1db30", + :categories ["Mexican" "Gastro Pub"]} + "Cam's Mexican Gastro Pub is a family-friendly and acceptable place to people-watch weekend evenings." + {:small + "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/small.jpg", + :medium + "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/med.jpg", + :large + "http://cloudfront.net/a6bddb27-880a-46da-b824-4449b2389a73/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "facebook", + :facebook-photo-id "f9afa193-7158-49d9-9f8c-42542c1e47dd", + :url + "http://facebook.com/photos/f9afa193-7158-49d9-9f8c-42542c1e47dd"} + "Nob Hill Gluten-Free Coffee House is a fantastic and historical place to have a after-work cocktail with your pet toucan." + {:small + "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/small.jpg", + :medium + "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/med.jpg", + :large + "http://cloudfront.net/c3c254d4-4a06-472a-8373-c96d1cf13ca1/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "yelp", + :yelp-photo-id "4f557d5b-b69d-48d1-814a-d4cac246e72c", + :categories ["British" "Food Truck"]} + "Rasta's British Food Truck is a fantastic and underappreciated place to sip a glass of expensive wine when hungover." + {:small + "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/small.jpg", + :medium + "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/med.jpg", + :large + "http://cloudfront.net/1f6bfab8-196f-41cb-9abb-55fcf5ce501b/large.jpg"} + {:name "Rasta's British Food Truck", + :categories ["British" "Food Truck"], + :phone "415-958-9031", + :id "b6616c97-01d0-488f-a855-bcd6efe2b899"}] + [{:service "facebook", + :facebook-photo-id "0927d96d-e419-49a9-bd61-cb6bb7f46a33", + :url + "http://facebook.com/photos/0927d96d-e419-49a9-bd61-cb6bb7f46a33"} + "Pacific Heights Pizza Bakery is a delicious and amazing place to watch the Giants game weekday afternoons." + {:small + "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/small.jpg", + :medium + "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/med.jpg", + :large + "http://cloudfront.net/44645780-38d0-4de9-9d8e-468b9f237a5f/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "facebook", + :facebook-photo-id "61efe486-455b-4327-995f-235de7d75f5a", + :url + "http://facebook.com/photos/61efe486-455b-4327-995f-235de7d75f5a"} + "Marina No-MSG Sushi is a atmospheric and historical place to sip Champagne on public holidays." + {:small + "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/small.jpg", + :medium + "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/med.jpg", + :large + "http://cloudfront.net/679db911-40ba-4250-9b11-46cc3d1a135f/large.jpg"} + {:name "Marina No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-856-5937", + :id "d51013a3-8547-4705-a5f0-cb11d8206481"}] + [{:service "facebook", + :facebook-photo-id "f13a16b2-3177-4827-bc81-3abccba5506b", + :url + "http://facebook.com/photos/f13a16b2-3177-4827-bc81-3abccba5506b"} + "Marina Cage-Free Liquor Store is a well-decorated and delicious place to nurse a hangover on a Tuesday afternoon." + {:small + "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/small.jpg", + :medium + "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/med.jpg", + :large + "http://cloudfront.net/bbbe5406-2b10-4af1-a63d-6c98fe101d90/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "facebook", + :facebook-photo-id "2b761a56-e66d-4cad-9117-ef6b0b633720", + :url + "http://facebook.com/photos/2b761a56-e66d-4cad-9117-ef6b0b633720"} + "Lower Pac Heights Deep-Dish Liquor Store is a horrible and exclusive place to have brunch weekend mornings." + {:small + "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/small.jpg", + :medium + "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/med.jpg", + :large + "http://cloudfront.net/30f58fc0-8e79-4c25-bffa-5cf022b984a9/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Liquor Store", + :categories ["Deep-Dish" "Liquor Store"], + :phone "415-497-3039", + :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"}] + [{:service "facebook", + :facebook-photo-id "9b584eeb-9715-4874-8e56-8a69838e6ddf", + :url + "http://facebook.com/photos/9b584eeb-9715-4874-8e56-8a69838e6ddf"} + "Mission British Café is a world-famous and groovy place to pitch an investor on public holidays." + {:small + "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/small.jpg", + :medium + "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/med.jpg", + :large + "http://cloudfront.net/a75b8799-e41c-4a50-b19c-b3cb21ef8ae6/large.jpg"} + {:name "Mission British Café", + :categories ["British" "Café"], + :phone "415-715-7004", + :id "c99899e3-439c-4444-9dc4-5598632aec8d"}] + [{:service "flare", :username "sameer"} + "Oakland American Grill is a fantastic and well-decorated place to have a birthday party weekday afternoons." + {:small + "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/small.jpg", + :medium + "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/med.jpg", + :large + "http://cloudfront.net/e255e8ea-3015-46bd-94db-198385ae5f7c/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "foursquare", + :foursquare-photo-id "35681dab-4f25-420d-9a65-915d233817f0", + :mayor "sameer"} + "Kyle's Low-Carb Grill is a overrated and fantastic place to have breakfast with your pet dog." + {:small + "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/small.jpg", + :medium + "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/med.jpg", + :large + "http://cloudfront.net/629404a1-ed36-4288-95e0-e57f6142990f/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "foursquare", + :foursquare-photo-id "1714302b-8629-4b18-ab25-2368ad1e4568", + :mayor "biggie"} + "Nob Hill Gluten-Free Coffee House is a underappreciated and family-friendly place to take a date after baseball games." + {:small + "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/small.jpg", + :medium + "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/med.jpg", + :large + "http://cloudfront.net/5d8da40a-6415-4dcc-8f51-f8e3ef99f332/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "foursquare", + :foursquare-photo-id "c1e232ec-e10f-4ad6-b932-84cd854ee3c2", + :mayor "amy"} + "Alcatraz Cage-Free Restaurant is a underappreciated and groovy place to have a drink when hungover." + {:small + "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/small.jpg", + :medium + "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/med.jpg", + :large + "http://cloudfront.net/92e9ca72-bdf1-4e8a-8f21-709644a8a848/large.jpg"} + {:name "Alcatraz Cage-Free Restaurant", + :categories ["Cage-Free" "Restaurant"], + :phone "415-568-0312", + :id "fe0c7f8e-4937-4a76-bda4-44ad89c5231c"}] + [{:service "yelp", + :yelp-photo-id "23e11b87-eb0d-48a8-9eb4-0e7b0453f4d7", + :categories ["Low-Carb" "Grill"]} + "Kyle's Low-Carb Grill is a decent and well-decorated place to sip a glass of expensive wine in July." + {:small + "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/small.jpg", + :medium + "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/med.jpg", + :large + "http://cloudfront.net/2e81de9d-a366-4756-ab0f-c88b1eeb15cc/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "twitter", + :mentions ["@lower_pac_heights_deep_dish_liquor_store"], + :tags ["#deep-dish" "#liquor" "#store"], + :username "sameer"} + "Lower Pac Heights Deep-Dish Liquor Store is a modern and well-decorated place to pitch an investor after baseball games." + {:small + "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/small.jpg", + :medium + "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/med.jpg", + :large + "http://cloudfront.net/28f821ba-fc0c-43d5-90aa-014997910ff4/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Liquor Store", + :categories ["Deep-Dish" "Liquor Store"], + :phone "415-497-3039", + :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"}] + [{:service "twitter", + :mentions ["@kyles_chinese_restaurant"], + :tags ["#chinese" "#restaurant"], + :username "tupac"} + "Kyle's Chinese Restaurant is a decent and world-famous place to drink a craft beer in the fall." + {:small + "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/small.jpg", + :medium + "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/med.jpg", + :large + "http://cloudfront.net/0a8ca876-81d1-43de-928c-6dfaaa99a4d9/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "facebook", + :facebook-photo-id "f132a200-55f9-4ae8-b61e-4e932287c502", + :url + "http://facebook.com/photos/f132a200-55f9-4ae8-b61e-4e932287c502"} + "Lucky's Japanese Bar & Grill is a atmospheric and decent place to watch the Warriors game in the spring." + {:small + "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/small.jpg", + :medium + "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/med.jpg", + :large + "http://cloudfront.net/81d4c210-2aad-44be-a5a5-554d0c68a1b3/large.jpg"} + {:name "Lucky's Japanese Bar & Grill", + :categories ["Japanese" "Bar & Grill"], + :phone "415-816-1300", + :id "602d574a-6fd3-44df-9bac-e71ce1ab5eb4"}] + [{:service "facebook", + :facebook-photo-id "3f37d940-c75b-41f2-b13f-e5b0828843af", + :url + "http://facebook.com/photos/3f37d940-c75b-41f2-b13f-e5b0828843af"} + "Marina Japanese Liquor Store is a fantastic and modern place to have a birthday party on a Tuesday afternoon." + {:small + "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/small.jpg", + :medium + "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/med.jpg", + :large + "http://cloudfront.net/e7db8648-76ac-4f59-aa43-f8e980e929fe/large.jpg"} + {:name "Marina Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-587-9819", + :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"}] + [{:service "facebook", + :facebook-photo-id "27189121-e22e-4dd6-85da-8880be9c513c", + :url + "http://facebook.com/photos/27189121-e22e-4dd6-85da-8880be9c513c"} + "Kyle's Chinese Restaurant is a great and delicious place to conduct a business meeting on public holidays." + {:small + "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/small.jpg", + :medium + "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/med.jpg", + :large + "http://cloudfront.net/1620fe7b-8e84-46d4-8807-fa47f52be5bb/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "twitter", + :mentions ["@haight_soul_food_hotel_&_restaurant"], + :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], + :username "cam_saul"} + "Haight Soul Food Hotel & Restaurant is a world-famous and atmospheric place to sip a glass of expensive wine Friday nights." + {:small + "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/small.jpg", + :medium + "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/med.jpg", + :large + "http://cloudfront.net/8b7f40e0-1ab0-4f11-8313-04fb6dc3c1a9/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "foursquare", + :foursquare-photo-id "3bbd4357-aba9-4a8e-837c-c57e42dee4e3", + :mayor "biggie"} + "Haight Soul Food Café is a wonderful and underground place to watch the Warriors game on a Tuesday afternoon." + {:small + "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/small.jpg", + :medium + "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/med.jpg", + :large + "http://cloudfront.net/68e3313a-d8cf-4de6-9d85-393b5e881259/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "facebook", + :facebook-photo-id "39e3b7a2-6fcf-4e90-b038-fafcc1e528f6", + :url + "http://facebook.com/photos/39e3b7a2-6fcf-4e90-b038-fafcc1e528f6"} + "Sunset Homestyle Grill is a atmospheric and great place to have brunch weekend mornings." + {:small + "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/small.jpg", + :medium + "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/med.jpg", + :large + "http://cloudfront.net/14743cfe-f2e7-4532-bd72-3ef14a277fa6/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "yelp", + :yelp-photo-id "5bfe141b-4680-45de-be48-4eb8cdb8b791", + :categories ["GMO-Free" "Pop-Up Food Stand"]} + "Sameer's GMO-Free Pop-Up Food Stand is a wonderful and fantastic place to have breakfast the first Sunday of the month." + {:small + "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/small.jpg", + :medium + "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/med.jpg", + :large + "http://cloudfront.net/a9e803dd-7cdf-47ac-8314-3f12eee7fdfc/large.jpg"} + {:name "Sameer's GMO-Free Pop-Up Food Stand", + :categories ["GMO-Free" "Pop-Up Food Stand"], + :phone "415-217-7891", + :id "a829efc7-7e03-4e73-b072-83d10d1e3953"}] + [{:service "flare", :username "tupac"} + "Cam's Old-Fashioned Coffee House is a modern and family-friendly place to take visiting friends and relatives Friday nights." + {:small + "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/small.jpg", + :medium + "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/med.jpg", + :large + "http://cloudfront.net/490f4943-6aaa-422c-8a7a-5996d806b67f/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "flare", :username "mandy"} + "Tenderloin Cage-Free Sushi is a overrated and overrated place to watch the Warriors game weekend mornings." + {:small + "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/small.jpg", + :medium + "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/med.jpg", + :large + "http://cloudfront.net/6cf1e5bd-3fd0-4ee4-a35a-fbc60cc2d8be/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "facebook", + :facebook-photo-id "331f4c85-0b7b-4106-94be-df6628e6fb09", + :url + "http://facebook.com/photos/331f4c85-0b7b-4106-94be-df6628e6fb09"} + "Tenderloin Red White & Blue Pizzeria is a atmospheric and wonderful place to conduct a business meeting Friday nights." + {:small + "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/small.jpg", + :medium + "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/med.jpg", + :large + "http://cloudfront.net/2b9bbbcb-b715-4429-b139-2855600d721e/large.jpg"} + {:name "Tenderloin Red White & Blue Pizzeria", + :categories ["Red White & Blue" "Pizzeria"], + :phone "415-719-8143", + :id "eba3dbcd-100a-4f38-a701-e0dec157f437"}] + [{:service "foursquare", + :foursquare-photo-id "3dcc3e91-1555-476a-9b3b-7834c2f8f1ba", + :mayor "biggie"} + "Haight Soul Food Pop-Up Food Stand is a overrated and amazing place to catch a bite to eat weekday afternoons." + {:small + "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/small.jpg", + :medium + "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/med.jpg", + :large + "http://cloudfront.net/da6fff65-7f9d-403a-b905-d21cd0306f69/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "twitter", + :mentions ["@luckys_old_fashioned_eatery"], + :tags ["#old-fashioned" "#eatery"], + :username "lucky_pigeon"} + "Lucky's Old-Fashioned Eatery is a delicious and family-friendly place to drink a craft beer the first Sunday of the month." + {:small + "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/small.jpg", + :medium + "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/med.jpg", + :large + "http://cloudfront.net/4437ee2a-9520-4357-a9d5-c321055a249f/large.jpg"} + {:name "Lucky's Old-Fashioned Eatery", + :categories ["Old-Fashioned" "Eatery"], + :phone "415-362-2338", + :id "71dc221c-6e82-4d06-8709-93293121b1da"}] + [{:service "twitter", + :mentions ["@tenderloin_japanese_ice_cream_truck"], + :tags ["#japanese" "#ice" "#cream" "#truck"], + :username "amy"} + "Tenderloin Japanese Ice Cream Truck is a well-decorated and decent place to have breakfast with friends." + {:small + "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/small.jpg", + :medium + "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/med.jpg", + :large + "http://cloudfront.net/04f74c1f-c835-46f4-8c7f-823042f2a091/large.jpg"} + {:name "Tenderloin Japanese Ice Cream Truck", + :categories ["Japanese" "Ice Cream Truck"], + :phone "415-856-0371", + :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"}] + [{:service "facebook", + :facebook-photo-id "b5874d46-0247-4515-bd96-e8cc562c256d", + :url + "http://facebook.com/photos/b5874d46-0247-4515-bd96-e8cc562c256d"} + "Oakland American Grill is a wonderful and underappreciated place to watch the Giants game with friends." + {:small + "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/small.jpg", + :medium + "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/med.jpg", + :large + "http://cloudfront.net/11b1c0e3-005a-414a-bd5a-e05880d277d5/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "facebook", + :facebook-photo-id "dafa1931-d938-44cf-bd12-4321d5c22407", + :url + "http://facebook.com/photos/dafa1931-d938-44cf-bd12-4321d5c22407"} + "Tenderloin Paleo Hotel & Restaurant is a classic and decent place to sip Champagne the first Sunday of the month." + {:small + "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/small.jpg", + :medium + "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/med.jpg", + :large + "http://cloudfront.net/ed01632a-a03e-4f9a-8949-0b06c690abd5/large.jpg"} + {:name "Tenderloin Paleo Hotel & Restaurant", + :categories ["Paleo" "Hotel & Restaurant"], + :phone "415-402-1652", + :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"}] + [{:service "twitter", + :mentions ["@rastas_paleo_café"], + :tags ["#paleo" "#café"], + :username "jessica"} + "Rasta's Paleo Café is a well-decorated and exclusive place to sip a glass of expensive wine on a Tuesday afternoon." + {:small + "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/small.jpg", + :medium + "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/med.jpg", + :large + "http://cloudfront.net/42b88fc6-e651-4ca3-9d53-e750df273b71/large.jpg"} + {:name "Rasta's Paleo Café", + :categories ["Paleo" "Café"], + :phone "415-392-6341", + :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"}] + [{:service "foursquare", + :foursquare-photo-id "1a06dc7a-c6a4-47e6-a93d-131e99c481da", + :mayor "lucky_pigeon"} + "Lower Pac Heights Deep-Dish Ice Cream Truck is a family-friendly and atmospheric place to nurse a hangover on Taco Tuesday." + {:small + "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/small.jpg", + :medium + "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/med.jpg", + :large + "http://cloudfront.net/6ff7a334-5c10-4be7-8cdf-320f10f12f6e/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", + :categories ["Deep-Dish" "Ice Cream Truck"], + :phone "415-495-1414", + :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"}] + [{:service "foursquare", + :foursquare-photo-id "416a5da5-6d01-4a16-956d-dcb85ce88bd5", + :mayor "joe"} + "Joe's Homestyle Eatery is a popular and atmospheric place to conduct a business meeting on a Tuesday afternoon." + {:small + "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/small.jpg", + :medium + "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/med.jpg", + :large + "http://cloudfront.net/42fa4469-19ba-41a2-816f-eeedf65664e5/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "foursquare", + :foursquare-photo-id "3b06925f-553a-4117-9864-b3e35d81d9e0", + :mayor "tupac"} + "Lucky's Low-Carb Coffee House is a exclusive and overrated place to pitch an investor on a Tuesday afternoon." + {:small + "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/small.jpg", + :medium + "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/med.jpg", + :large + "http://cloudfront.net/30379e31-47c0-4ba6-be2f-9a1cbd4baa63/large.jpg"} + {:name "Lucky's Low-Carb Coffee House", + :categories ["Low-Carb" "Coffee House"], + :phone "415-145-7107", + :id "81b0f944-f0ce-45e5-b84e-a924c441064a"}] + [{:service "foursquare", + :foursquare-photo-id "06e30d03-9b56-4820-adb4-c0b7ddde578b", + :mayor "jane"} + "Lower Pac Heights Deep-Dish Ice Cream Truck is a delicious and great place to have a drink on Saturday night." + {:small + "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/small.jpg", + :medium + "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/med.jpg", + :large + "http://cloudfront.net/9766eef8-6548-4a83-ab8d-ce023b2681c9/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", + :categories ["Deep-Dish" "Ice Cream Truck"], + :phone "415-495-1414", + :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"}] + [{:service "twitter", + :mentions ["@lower_pac_heights_cage_free_coffee_house"], + :tags ["#cage-free" "#coffee" "#house"], + :username "biggie"} + "Lower Pac Heights Cage-Free Coffee House is a horrible and swell place to people-watch the first Sunday of the month." + {:small + "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/small.jpg", + :medium + "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/med.jpg", + :large + "http://cloudfront.net/0bf95c7a-44aa-4a9d-8580-c3b8f79147d2/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "facebook", + :facebook-photo-id "16a05fd8-120e-4f96-9857-4f340611e5f9", + :url + "http://facebook.com/photos/16a05fd8-120e-4f96-9857-4f340611e5f9"} + "Marina Cage-Free Liquor Store is a wonderful and acceptable place to watch the Warriors game during winter." + {:small + "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/small.jpg", + :medium + "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/med.jpg", + :large + "http://cloudfront.net/cb4a1c99-85d3-401c-b015-48839206ebfe/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "facebook", + :facebook-photo-id "4022dab6-225b-4677-be1d-7c201233bdee", + :url + "http://facebook.com/photos/4022dab6-225b-4677-be1d-7c201233bdee"} + "SoMa Japanese Churros is a fantastic and swell place to drink a craft beer in the spring." + {:small + "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/small.jpg", + :medium + "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/med.jpg", + :large + "http://cloudfront.net/20a5ff27-ccd3-4c02-8cf5-7bc11e03b47d/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "facebook", + :facebook-photo-id "7e9a5a67-48ab-4a54-821c-1a6f59a3ea92", + :url + "http://facebook.com/photos/7e9a5a67-48ab-4a54-821c-1a6f59a3ea92"} + "Nob Hill Free-Range Ice Cream Truck is a groovy and fantastic place to have brunch on a Tuesday afternoon." + {:small + "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/small.jpg", + :medium + "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/med.jpg", + :large + "http://cloudfront.net/2e8ef910-73c2-45d1-8b15-fb2f4913d094/large.jpg"} + {:name "Nob Hill Free-Range Ice Cream Truck", + :categories ["Free-Range" "Ice Cream Truck"], + :phone "415-787-4049", + :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"}] + [{:service "facebook", + :facebook-photo-id "a868cdab-32c0-4939-a024-463412457bca", + :url + "http://facebook.com/photos/a868cdab-32c0-4939-a024-463412457bca"} + "SoMa Old-Fashioned Pizzeria is a horrible and horrible place to conduct a business meeting on Taco Tuesday." + {:small + "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/small.jpg", + :medium + "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/med.jpg", + :large + "http://cloudfront.net/8fd7a773-2103-4d2a-8337-f965ad7bb41e/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@haight_soul_food_pop_up_food_stand"], + :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], + :username "amy"} + "Haight Soul Food Pop-Up Food Stand is a fantastic and family-friendly place to take a date with your pet dog." + {:small + "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/small.jpg", + :medium + "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/med.jpg", + :large + "http://cloudfront.net/252aa589-8ab3-48d8-861a-bfefd422b257/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "foursquare", + :foursquare-photo-id "a3ad3c09-99fc-45e3-b786-0b293eaa525d", + :mayor "jane"} + "Pacific Heights Free-Range Eatery is a atmospheric and modern place to nurse a hangover on Saturday night." + {:small + "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/small.jpg", + :medium + "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/med.jpg", + :large + "http://cloudfront.net/860991ab-b4ca-4a5b-93fb-7a6cd7a6d208/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "facebook", + :facebook-photo-id "f8d9a1ea-707f-4e4d-9a1b-fbc953f50361", + :url + "http://facebook.com/photos/f8d9a1ea-707f-4e4d-9a1b-fbc953f50361"} + "Sameer's GMO-Free Restaurant is a underground and swell place to watch the Warriors game on Thursdays." + {:small + "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/small.jpg", + :medium + "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/med.jpg", + :large + "http://cloudfront.net/51846ade-98ab-4b6a-b783-2714d9c751d0/large.jpg"} + {:name "Sameer's GMO-Free Restaurant", + :categories ["GMO-Free" "Restaurant"], + :phone "415-128-9430", + :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"}] + [{:service "facebook", + :facebook-photo-id "50e226aa-7b65-450e-9248-040c24bf3577", + :url + "http://facebook.com/photos/50e226aa-7b65-450e-9248-040c24bf3577"} + "Rasta's European Taqueria is a acceptable and groovy place to have a birthday party Friday nights." + {:small + "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/small.jpg", + :medium + "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/med.jpg", + :large + "http://cloudfront.net/ae49f9bb-7498-44ea-bfaf-e9d4c2f7b7f3/large.jpg"} + {:name "Rasta's European Taqueria", + :categories ["European" "Taqueria"], + :phone "415-631-1599", + :id "cb472880-ee6e-46e3-bd58-22cf33109aba"}] + [{:service "yelp", + :yelp-photo-id "e6266710-a32b-4da5-8d9d-6b0440596c10", + :categories ["Old-Fashioned" "Coffee House"]} + "Cam's Old-Fashioned Coffee House is a groovy and classic place to take a date weekend evenings." + {:small + "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/small.jpg", + :medium + "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/med.jpg", + :large + "http://cloudfront.net/ba2b9659-02d5-4df5-849d-b24d342176e6/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "twitter", + :mentions ["@polk_st._mexican_coffee_house"], + :tags ["#mexican" "#coffee" "#house"], + :username "sameer"} + "Polk St. Mexican Coffee House is a exclusive and well-decorated place to people-watch weekend evenings." + {:small + "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/small.jpg", + :medium + "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/med.jpg", + :large + "http://cloudfront.net/2cfd3695-50fd-46fe-b141-07491a10ac99/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "foursquare", + :foursquare-photo-id "02362879-deac-452e-b656-976edb806e8b", + :mayor "rasta_toucan"} + "Tenderloin Gluten-Free Bar & Grill is a swell and exclusive place to have brunch weekend evenings." + {:small + "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/small.jpg", + :medium + "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/med.jpg", + :large + "http://cloudfront.net/cfb304d9-bb56-4b72-b9e7-df983f1fb9e1/large.jpg"} + {:name "Tenderloin Gluten-Free Bar & Grill", + :categories ["Gluten-Free" "Bar & Grill"], + :phone "415-904-0956", + :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"}] + [{:service "facebook", + :facebook-photo-id "fc57f41a-9684-4b88-bb8b-9c223eeb47ef", + :url + "http://facebook.com/photos/fc57f41a-9684-4b88-bb8b-9c223eeb47ef"} + "Sunset Homestyle Grill is a world-famous and fantastic place to meet new friends on public holidays." + {:small + "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/small.jpg", + :medium + "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/med.jpg", + :large + "http://cloudfront.net/a1ccd1c5-5144-475a-a151-450c3cc66742/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "twitter", + :mentions ["@haight_chinese_gastro_pub"], + :tags ["#chinese" "#gastro" "#pub"], + :username "bob"} + "Haight Chinese Gastro Pub is a exclusive and underappreciated place to drink a craft beer the second Saturday of the month." + {:small + "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/small.jpg", + :medium + "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/med.jpg", + :large + "http://cloudfront.net/1ac6a807-49a0-4c57-90c7-4ded792903fe/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "foursquare", + :foursquare-photo-id "dc0709af-da58-4c33-9919-0c3e42e4e0d7", + :mayor "rasta_toucan"} + "Haight Soul Food Café is a great and popular place to pitch an investor during winter." + {:small + "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/small.jpg", + :medium + "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/med.jpg", + :large + "http://cloudfront.net/9e69fff5-926e-4fdd-a160-1dc607ab06a0/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "foursquare", + :foursquare-photo-id "e415f4d4-08f0-4ee0-abab-d2df0bf41fa1", + :mayor "cam_saul"} + "SF Deep-Dish Eatery is a horrible and great place to drink a craft beer on Taco Tuesday." + {:small + "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/small.jpg", + :medium + "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/med.jpg", + :large + "http://cloudfront.net/b76358fa-f3cc-470a-9f9f-91be479e7c77/large.jpg"} + {:name "SF Deep-Dish Eatery", + :categories ["Deep-Dish" "Eatery"], + :phone "415-476-9257", + :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"}] + [{:service "flare", :username "jane"} + "Pacific Heights Free-Range Eatery is a groovy and historical place to have a birthday party in the spring." + {:small + "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/small.jpg", + :medium + "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/med.jpg", + :large + "http://cloudfront.net/b3499888-ccc2-456c-875b-c1b16e6a9fab/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "foursquare", + :foursquare-photo-id "a3de9324-b821-47ec-a07d-36c9c0702400", + :mayor "lucky_pigeon"} + "Haight Chinese Gastro Pub is a world-famous and popular place to take visiting friends and relatives the second Saturday of the month." + {:small + "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/small.jpg", + :medium + "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/med.jpg", + :large + "http://cloudfront.net/98170d41-1145-4c3c-8e4e-ecf65ac1ff26/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "yelp", + :yelp-photo-id "6747488f-9287-40f4-b676-0116b0973bec", + :categories ["Soul Food" "Pop-Up Food Stand"]} + "Haight Soul Food Pop-Up Food Stand is a great and wonderful place to catch a bite to eat weekend evenings." + {:small + "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/small.jpg", + :medium + "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/med.jpg", + :large + "http://cloudfront.net/3187fbfa-fa2c-4109-af80-77b4e0afe5bd/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "yelp", + :yelp-photo-id "2e35c934-2f4c-417f-a6ab-56a8bda58b16", + :categories ["Red White & Blue" "Bar & Grill"]} + "Pacific Heights Red White & Blue Bar & Grill is a horrible and decent place to watch the Giants game in July." + {:small + "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/small.jpg", + :medium + "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/med.jpg", + :large + "http://cloudfront.net/919615dc-461e-4f34-ac5f-97253d515021/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "flare", :username "tupac"} + "Rasta's Old-Fashioned Pop-Up Food Stand is a exclusive and family-friendly place to conduct a business meeting on public holidays." + {:small + "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/small.jpg", + :medium + "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/med.jpg", + :large + "http://cloudfront.net/0e16f689-a170-4fe0-9e15-8fd838abdf09/large.jpg"} + {:name "Rasta's Old-Fashioned Pop-Up Food Stand", + :categories ["Old-Fashioned" "Pop-Up Food Stand"], + :phone "415-942-1875", + :id "9fd8b920-a877-4888-86bf-578b2724ac4e"}] + [{:service "foursquare", + :foursquare-photo-id "9f9fdd2d-a3d1-4bc1-b706-7e94d8ea2042", + :mayor "rasta_toucan"} + "Mission Free-Range Liquor Store is a groovy and delicious place to conduct a business meeting in June." + {:small + "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/small.jpg", + :medium + "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/med.jpg", + :large + "http://cloudfront.net/d72b00cf-fc40-493a-9f27-aab6ab438e38/large.jpg"} + {:name "Mission Free-Range Liquor Store", + :categories ["Free-Range" "Liquor Store"], + :phone "415-041-3816", + :id "6e665924-8e2c-42ab-af58-23a27f017e37"}] + [{:service "twitter", + :mentions ["@kyles_european_churros"], + :tags ["#european" "#churros"], + :username "jane"} + "Kyle's European Churros is a underappreciated and family-friendly place to watch the Giants game during summer." + {:small + "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/small.jpg", + :medium + "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/med.jpg", + :large + "http://cloudfront.net/046e8027-3830-4251-a7a9-b3039b4300f9/large.jpg"} + {:name "Kyle's European Churros", + :categories ["European" "Churros"], + :phone "415-233-8392", + :id "5270240c-6e6e-4512-9344-3dc497d6ea49"}] + [{:service "flare", :username "rasta_toucan"} + "Haight Soul Food Hotel & Restaurant is a classic and decent place to people-watch on Taco Tuesday." + {:small + "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/small.jpg", + :medium + "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/med.jpg", + :large + "http://cloudfront.net/e23afb15-0deb-4060-a5ff-ec8497816adf/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "yelp", + :yelp-photo-id "22e2d196-931a-4251-8fce-a5492c304185", + :categories ["Old-Fashioned" "Pizzeria"]} + "SoMa Old-Fashioned Pizzeria is a world-famous and classic place to catch a bite to eat in June." + {:small + "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/small.jpg", + :medium + "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/med.jpg", + :large + "http://cloudfront.net/926c9016-ec2e-4ef3-b40f-fd404e573d94/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "foursquare", + :foursquare-photo-id "0211f4dc-c9db-4505-8b9d-fd5abf151ada", + :mayor "rasta_toucan"} + "Haight Chinese Gastro Pub is a modern and underground place to watch the Warriors game weekday afternoons." + {:small + "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/small.jpg", + :medium + "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/med.jpg", + :large + "http://cloudfront.net/b6c37f33-b6c7-4684-b96e-54b5ee77cac2/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "foursquare", + :foursquare-photo-id "dc22d4ab-1f2a-4ad2-babf-9b5b96cc65d7", + :mayor "joe"} + "Cam's Old-Fashioned Coffee House is a decent and modern place to conduct a business meeting Friday nights." + {:small + "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/small.jpg", + :medium + "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/med.jpg", + :large + "http://cloudfront.net/34c40a62-a21f-4110-a3a9-d633e2bad9da/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "foursquare", + :foursquare-photo-id "a033581b-09b2-487f-b78d-18af543bc0b6", + :mayor "rasta_toucan"} + "Market St. Gluten-Free Café is a classic and wonderful place to watch the Warriors game in June." + {:small + "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/small.jpg", + :medium + "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/med.jpg", + :large + "http://cloudfront.net/b8408489-88ec-4ff0-9c18-1dc647aa70aa/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "foursquare", + :foursquare-photo-id "44854a6b-c709-4f0f-ab52-f4b5982ec2ee", + :mayor "amy"} + "Tenderloin Paleo Hotel & Restaurant is a world-famous and swell place to sip a glass of expensive wine weekend mornings." + {:small + "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/small.jpg", + :medium + "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/med.jpg", + :large + "http://cloudfront.net/00a4a70a-3f26-46a7-b952-a08e9ae1281f/large.jpg"} + {:name "Tenderloin Paleo Hotel & Restaurant", + :categories ["Paleo" "Hotel & Restaurant"], + :phone "415-402-1652", + :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"}] + [{:service "foursquare", + :foursquare-photo-id "73b07d4f-8de1-4c30-be6e-65e891d60dcd", + :mayor "sameer"} + "Haight Soul Food Sushi is a swell and acceptable place to nurse a hangover on Saturday night." + {:small + "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/small.jpg", + :medium + "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/med.jpg", + :large + "http://cloudfront.net/d14c0a83-f0a8-4bee-b760-4c91bbd44c21/large.jpg"} + {:name "Haight Soul Food Sushi", + :categories ["Soul Food" "Sushi"], + :phone "415-371-8026", + :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"}] + [{:service "flare", :username "bob"} + "SoMa Japanese Churros is a world-famous and modern place to drink a craft beer when hungover." + {:small + "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/small.jpg", + :medium + "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/med.jpg", + :large + "http://cloudfront.net/d058c540-7cba-4cdd-82a1-1c19bb6e0926/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "facebook", + :facebook-photo-id "7888806a-6bcb-43e4-89a6-ee06096d8f6f", + :url + "http://facebook.com/photos/7888806a-6bcb-43e4-89a6-ee06096d8f6f"} + "Marina Low-Carb Food Truck is a fantastic and decent place to watch the Giants game in the spring." + {:small + "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/small.jpg", + :medium + "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/med.jpg", + :large + "http://cloudfront.net/7984c64f-8b50-49fe-bb19-d96b88d692eb/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "yelp", + :yelp-photo-id "e63bccf1-3d28-46c5-a3ca-7b8b8063a785", + :categories ["Paleo" "Churros"]} + "Rasta's Paleo Churros is a historical and acceptable place to catch a bite to eat on public holidays." + {:small + "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/small.jpg", + :medium + "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/med.jpg", + :large + "http://cloudfront.net/d04563bd-9802-4fd9-bcb3-132544b06612/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "foursquare", + :foursquare-photo-id "2ae13e5f-bfef-46cb-8bfb-f568f5fa383d", + :mayor "amy"} + "Sameer's GMO-Free Pop-Up Food Stand is a wonderful and horrible place to drink a craft beer on Thursdays." + {:small + "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/small.jpg", + :medium + "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/med.jpg", + :large + "http://cloudfront.net/4a743cdf-ef0f-4b8c-839f-c7691449fe9e/large.jpg"} + {:name "Sameer's GMO-Free Pop-Up Food Stand", + :categories ["GMO-Free" "Pop-Up Food Stand"], + :phone "415-217-7891", + :id "a829efc7-7e03-4e73-b072-83d10d1e3953"}] + [{:service "yelp", + :yelp-photo-id "03bfbbbc-ca84-459a-a81e-3a2e08986052", + :categories ["Cage-Free" "Liquor Store"]} + "Marina Cage-Free Liquor Store is a delicious and acceptable place to pitch an investor on Taco Tuesday." + {:small + "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/small.jpg", + :medium + "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/med.jpg", + :large + "http://cloudfront.net/aa31e351-b952-4757-80ce-5563659b677e/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "yelp", + :yelp-photo-id "4b790ee2-1c3f-4ee6-bd5d-9debea0377e1", + :categories ["British" "Café"]} + "Mission British Café is a decent and decent place to nurse a hangover after baseball games." + {:small + "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/small.jpg", + :medium + "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/med.jpg", + :large + "http://cloudfront.net/dd60f1e4-5954-4bb3-b826-684715614901/large.jpg"} + {:name "Mission British Café", + :categories ["British" "Café"], + :phone "415-715-7004", + :id "c99899e3-439c-4444-9dc4-5598632aec8d"}] + [{:service "foursquare", + :foursquare-photo-id "a8cc052d-7c49-4c06-b81d-81d5208ed90c", + :mayor "jessica"} + "Sameer's Pizza Liquor Store is a great and popular place to nurse a hangover during summer." + {:small + "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/small.jpg", + :medium + "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/med.jpg", + :large + "http://cloudfront.net/b579ac79-99e9-4be9-861a-36e540f0d335/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "foursquare", + :foursquare-photo-id "5d6e0806-1f8c-43c7-84c4-2209c132d438", + :mayor "mandy"} + "Pacific Heights Irish Grill is a overrated and underground place to people-watch in June." + {:small + "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/small.jpg", + :medium + "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/med.jpg", + :large + "http://cloudfront.net/11e72119-4d0a-4136-b84f-46ce0d184767/large.jpg"} + {:name "Pacific Heights Irish Grill", + :categories ["Irish" "Grill"], + :phone "415-491-2202", + :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"}] + [{:service "foursquare", + :foursquare-photo-id "5b08d58b-61b1-464a-8380-0bf9d846a9be", + :mayor "sameer"} + "SoMa Old-Fashioned Pizzeria is a exclusive and acceptable place to watch the Giants game in June." + {:small + "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/small.jpg", + :medium + "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/med.jpg", + :large + "http://cloudfront.net/a17fc0ad-3eb8-4b64-877e-4320b87f1ec5/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@mission_chinese_liquor_store"], + :tags ["#chinese" "#liquor" "#store"], + :username "lucky_pigeon"} + "Mission Chinese Liquor Store is a swell and well-decorated place to catch a bite to eat during summer." + {:small + "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/small.jpg", + :medium + "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/med.jpg", + :large + "http://cloudfront.net/1cb214d4-0140-4aa5-aa4c-259f6d980fea/large.jpg"} + {:name "Mission Chinese Liquor Store", + :categories ["Chinese" "Liquor Store"], + :phone "415-906-6919", + :id "00132b5b-31fc-46f0-a288-f547f23477ee"}] + [{:service "yelp", + :yelp-photo-id "4043626c-1296-45f8-ba01-eca54642defa", + :categories ["Soul Food" "Sushi"]} + "Haight Soul Food Sushi is a swell and underappreciated place to watch the Giants game in the spring." + {:small + "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/small.jpg", + :medium + "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/med.jpg", + :large + "http://cloudfront.net/88e6aee5-89e4-4b52-ac93-d721652a8d36/large.jpg"} + {:name "Haight Soul Food Sushi", + :categories ["Soul Food" "Sushi"], + :phone "415-371-8026", + :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"}] + [{:service "yelp", + :yelp-photo-id "9b425a8e-9bcb-40ce-98df-52e8c6e47978", + :categories ["Homestyle" "Pop-Up Food Stand"]} + "Market St. Homestyle Pop-Up Food Stand is a amazing and classic place to sip Champagne Friday nights." + {:small + "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/small.jpg", + :medium + "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/med.jpg", + :large + "http://cloudfront.net/d4d40457-4efa-414e-8741-a10843fea0fd/large.jpg"} + {:name "Market St. Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-213-3030", + :id "2d873280-e43d-449e-9940-af96ae7df718"}] + [{:service "facebook", + :facebook-photo-id "cd159f6b-6c5d-400e-90e9-c667d40cea43", + :url + "http://facebook.com/photos/cd159f6b-6c5d-400e-90e9-c667d40cea43"} + "Polk St. Deep-Dish Hotel & Restaurant is a well-decorated and fantastic place to take a date in June." + {:small + "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/small.jpg", + :medium + "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/med.jpg", + :large + "http://cloudfront.net/0f7bdbd7-40e6-4b97-81c0-daae0875c3b7/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "twitter", + :mentions ["@pacific_heights_red_white_&_blue_bar_&_grill"], + :tags ["#red" "#white" "#&" "#blue" "#bar" "#&" "#grill"], + :username "cam_saul"} + "Pacific Heights Red White & Blue Bar & Grill is a decent and family-friendly place to have a drink on Saturday night." + {:small + "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/small.jpg", + :medium + "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/med.jpg", + :large + "http://cloudfront.net/e7211862-740e-4100-919a-4fd221276ef1/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "foursquare", + :foursquare-photo-id "bad6706f-9387-4946-a65f-872b5055638b", + :mayor "tupac"} + "Haight European Grill is a wonderful and horrible place to sip Champagne with your pet dog." + {:small + "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/small.jpg", + :medium + "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/med.jpg", + :large + "http://cloudfront.net/f08661e3-3f7d-4e05-b3b7-25643b1478f1/large.jpg"} + {:name "Haight European Grill", + :categories ["European" "Grill"], + :phone "415-191-2778", + :id "7e6281f7-5b17-4056-ada0-85453247bc8f"}] + [{:service "twitter", + :mentions ["@cams_old_fashioned_coffee_house"], + :tags ["#old-fashioned" "#coffee" "#house"], + :username "rasta_toucan"} + "Cam's Old-Fashioned Coffee House is a underappreciated and family-friendly place to have a drink with friends." + {:small + "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/small.jpg", + :medium + "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/med.jpg", + :large + "http://cloudfront.net/83f2915e-edff-49bf-bb42-2236c634f0da/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "yelp", + :yelp-photo-id "dcc28cfa-c4aa-4c03-8985-48b72c682e06", + :categories ["Old-Fashioned" "Coffee House"]} + "Cam's Old-Fashioned Coffee House is a exclusive and fantastic place to have a birthday party weekend evenings." + {:small + "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/small.jpg", + :medium + "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/med.jpg", + :large + "http://cloudfront.net/2f4200f6-8a0e-4a51-a80d-2783be070731/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "flare", :username "cam_saul"} + "Mission Homestyle Churros is a well-decorated and exclusive place to have a after-work cocktail the first Sunday of the month." + {:small + "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/small.jpg", + :medium + "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/med.jpg", + :large + "http://cloudfront.net/2c0d54dc-414f-45c0-9844-643b05dda78d/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "yelp", + :yelp-photo-id "161da098-e1d5-43d2-9ca9-f10ca0b48008", + :categories ["Pizza" "Churros"]} + "Alcatraz Pizza Churros is a groovy and underappreciated place to take visiting friends and relatives on Taco Tuesday." + {:small + "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/small.jpg", + :medium + "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/med.jpg", + :large + "http://cloudfront.net/e566abbe-39c9-47a6-be61-1654ca23d783/large.jpg"} + {:name "Alcatraz Pizza Churros", + :categories ["Pizza" "Churros"], + :phone "415-754-7867", + :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"}] + [{:service "foursquare", + :foursquare-photo-id "a8cf3fba-ecc4-46b5-97de-35f953def90e", + :mayor "bob"} + "Polk St. Deep-Dish Hotel & Restaurant is a modern and swell place to have a birthday party on Saturday night." + {:small + "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/small.jpg", + :medium + "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/med.jpg", + :large + "http://cloudfront.net/3511bf6b-6066-4139-bde0-ce2a4ffee9bd/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "yelp", + :yelp-photo-id "7679858d-2f30-4af5-b620-e406eb0b2f73", + :categories ["Mexican" "Sushi"]} + "Rasta's Mexican Sushi is a swell and horrible place to conduct a business meeting in June." + {:small + "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/small.jpg", + :medium + "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/med.jpg", + :large + "http://cloudfront.net/5ec77779-ac5d-4c0e-8d7c-a83cb883db1b/large.jpg"} + {:name "Rasta's Mexican Sushi", + :categories ["Mexican" "Sushi"], + :phone "415-387-1284", + :id "e4912a22-e6ac-4806-8377-6497bf533a21"}] + [{:service "twitter", + :mentions ["@haight_chinese_gastro_pub"], + :tags ["#chinese" "#gastro" "#pub"], + :username "tupac"} + "Haight Chinese Gastro Pub is a underappreciated and overrated place to take a date weekend evenings." + {:small + "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/small.jpg", + :medium + "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/med.jpg", + :large + "http://cloudfront.net/10d9c169-e448-4c5a-bc35-d6c316f0ebf6/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "facebook", + :facebook-photo-id "ab85f630-7d6d-445f-9848-48461b588909", + :url + "http://facebook.com/photos/ab85f630-7d6d-445f-9848-48461b588909"} + "Alcatraz Modern Eatery is a underground and underground place to meet new friends on a Tuesday afternoon." + {:small + "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/small.jpg", + :medium + "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/med.jpg", + :large + "http://cloudfront.net/ea1f1ae6-f34f-429d-b3ae-f31d4eec1560/large.jpg"} + {:name "Alcatraz Modern Eatery", + :categories ["Modern" "Eatery"], + :phone "415-899-2965", + :id "bbfafaac-e825-4c4f-8655-f5e697148d9c"}] + [{:service "facebook", + :facebook-photo-id "050b4f87-87f7-45c2-aeb7-d02cae41b076", + :url + "http://facebook.com/photos/050b4f87-87f7-45c2-aeb7-d02cae41b076"} + "Haight Soul Food Café is a amazing and fantastic place to drink a craft beer with your pet dog." + {:small + "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/small.jpg", + :medium + "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/med.jpg", + :large + "http://cloudfront.net/0acfc578-9f98-4e18-aa00-b9f4913d5aa8/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "foursquare", + :foursquare-photo-id "f63d629b-0e13-4c23-8cc8-6b08ca2e8213", + :mayor "tupac"} + "Lucky's Gluten-Free Café is a swell and horrible place to watch the Giants game with your pet dog." + {:small + "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/small.jpg", + :medium + "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/med.jpg", + :large + "http://cloudfront.net/f2ce5a16-6524-49f1-aa01-c231e73d8e0e/large.jpg"} + {:name "Lucky's Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-740-2328", + :id "379af987-ad40-4a93-88a6-0233e1c14649"}] + [{:service "yelp", + :yelp-photo-id "ff896124-cf74-44f8-ab68-631810f8bbff", + :categories ["Modern" "Coffee House"]} + "Joe's Modern Coffee House is a exclusive and exclusive place to sip Champagne with your pet dog." + {:small + "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/small.jpg", + :medium + "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/med.jpg", + :large + "http://cloudfront.net/1388c27c-c987-4d8d-8f26-60094f8057e8/large.jpg"} + {:name "Joe's Modern Coffee House", + :categories ["Modern" "Coffee House"], + :phone "415-331-5269", + :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"}] + [{:service "foursquare", + :foursquare-photo-id "8608a711-9a31-4f23-a2b5-01de2b554df0", + :mayor "lucky_pigeon"} + "Oakland Low-Carb Bakery is a classic and groovy place to have breakfast on Taco Tuesday." + {:small + "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/small.jpg", + :medium + "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/med.jpg", + :large + "http://cloudfront.net/1fde679f-4f4d-4cd9-b4ba-33417d84f2bb/large.jpg"} + {:name "Oakland Low-Carb Bakery", + :categories ["Low-Carb" "Bakery"], + :phone "415-546-0101", + :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"}] + [{:service "foursquare", + :foursquare-photo-id "b9f30495-8334-43b1-bcbd-e3b9e8cab52a", + :mayor "cam_saul"} + "Mission Homestyle Churros is a exclusive and popular place to have a birthday party Friday nights." + {:small + "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/small.jpg", + :medium + "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/med.jpg", + :large + "http://cloudfront.net/d50c5351-db5f-409d-a3db-b17509974e6c/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "twitter", + :mentions ["@haight_mexican_restaurant"], + :tags ["#mexican" "#restaurant"], + :username "rasta_toucan"} + "Haight Mexican Restaurant is a swell and exclusive place to meet new friends with your pet dog." + {:small + "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/small.jpg", + :medium + "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/med.jpg", + :large + "http://cloudfront.net/86e0d356-dd46-477f-9dae-338c30ea5a2b/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "twitter", + :mentions ["@sameers_gmo_free_pop_up_food_stand"], + :tags ["#gmo-free" "#pop-up" "#food" "#stand"], + :username "bob"} + "Sameer's GMO-Free Pop-Up Food Stand is a great and decent place to have breakfast in June." + {:small + "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/small.jpg", + :medium + "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/med.jpg", + :large + "http://cloudfront.net/5c8a43de-d9a0-49a9-8cc6-eb9dea7800fb/large.jpg"} + {:name "Sameer's GMO-Free Pop-Up Food Stand", + :categories ["GMO-Free" "Pop-Up Food Stand"], + :phone "415-217-7891", + :id "a829efc7-7e03-4e73-b072-83d10d1e3953"}] + [{:service "foursquare", + :foursquare-photo-id "4549eb5a-9e17-4b08-bfb3-44acfcc9d494", + :mayor "tupac"} + "Cam's Old-Fashioned Coffee House is a delicious and modern place to people-watch on Taco Tuesday." + {:small + "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/small.jpg", + :medium + "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/med.jpg", + :large + "http://cloudfront.net/9a3e6c72-3ab0-4105-bde4-0c08ee753d40/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-868-2973", + :id "27592c2b-e682-44bb-be28-8e9a622becca"}] + [{:service "flare", :username "jane"} + "Cam's Mexican Gastro Pub is a great and swell place to drink a craft beer in June." + {:small + "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/small.jpg", + :medium + "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/med.jpg", + :large + "http://cloudfront.net/036ac2ce-65b1-4a03-aef3-6919a7789f00/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "twitter", + :mentions ["@sunset_homestyle_grill"], + :tags ["#homestyle" "#grill"], + :username "amy"} + "Sunset Homestyle Grill is a overrated and underappreciated place to nurse a hangover in the fall." + {:small + "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/small.jpg", + :medium + "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/med.jpg", + :large + "http://cloudfront.net/7b59a37b-1f2c-4cf1-a6dd-14bbf2111904/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "facebook", + :facebook-photo-id "01ba3c6f-4360-406a-b1d3-5e6ca8d28a5a", + :url + "http://facebook.com/photos/01ba3c6f-4360-406a-b1d3-5e6ca8d28a5a"} + "Polk St. Red White & Blue Café is a delicious and swell place to conduct a business meeting on Thursdays." + {:small + "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/small.jpg", + :medium + "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/med.jpg", + :large + "http://cloudfront.net/9aefced2-f658-485c-a1c5-ee5cbdbb42d7/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "facebook", + :facebook-photo-id "42762b62-7cff-4a54-a0f7-f16c1f42d81c", + :url + "http://facebook.com/photos/42762b62-7cff-4a54-a0f7-f16c1f42d81c"} + "Haight European Grill is a swell and overrated place to watch the Giants game with your pet dog." + {:small + "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/small.jpg", + :medium + "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/med.jpg", + :large + "http://cloudfront.net/b0c4fee2-d9cd-4183-8695-8466dd15c08d/large.jpg"} + {:name "Haight European Grill", + :categories ["European" "Grill"], + :phone "415-191-2778", + :id "7e6281f7-5b17-4056-ada0-85453247bc8f"}] + [{:service "twitter", + :mentions ["@haight_soul_food_pop_up_food_stand"], + :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], + :username "sameer"} + "Haight Soul Food Pop-Up Food Stand is a amazing and world-famous place to sip a glass of expensive wine weekend evenings." + {:small + "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/small.jpg", + :medium + "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/med.jpg", + :large + "http://cloudfront.net/1285fa6f-a990-46c0-ad26-443d959f183c/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "twitter", + :mentions ["@tenderloin_red_white_&_blue_pizzeria"], + :tags ["#red" "#white" "#&" "#blue" "#pizzeria"], + :username "jessica"} + "Tenderloin Red White & Blue Pizzeria is a decent and underappreciated place to have a drink on Taco Tuesday." + {:small + "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/small.jpg", + :medium + "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/med.jpg", + :large + "http://cloudfront.net/0229bb84-6bc0-4ed4-b3da-dc743a49c8c8/large.jpg"} + {:name "Tenderloin Red White & Blue Pizzeria", + :categories ["Red White & Blue" "Pizzeria"], + :phone "415-719-8143", + :id "eba3dbcd-100a-4f38-a701-e0dec157f437"}] + [{:service "twitter", + :mentions ["@sunset_homestyle_grill"], + :tags ["#homestyle" "#grill"], + :username "jane"} + "Sunset Homestyle Grill is a amazing and wonderful place to have brunch on a Tuesday afternoon." + {:small + "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/small.jpg", + :medium + "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/med.jpg", + :large + "http://cloudfront.net/4203f53b-6a89-4f14-a597-305c3b2a27a1/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "foursquare", + :foursquare-photo-id "b555bae0-96f0-492f-807d-484460d33f62", + :mayor "sameer"} + "Lucky's Old-Fashioned Eatery is a decent and acceptable place to have a birthday party on public holidays." + {:small + "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/small.jpg", + :medium + "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/med.jpg", + :large + "http://cloudfront.net/9ea7bca8-891b-4b8e-be13-6374a91604a9/large.jpg"} + {:name "Lucky's Old-Fashioned Eatery", + :categories ["Old-Fashioned" "Eatery"], + :phone "415-362-2338", + :id "71dc221c-6e82-4d06-8709-93293121b1da"}] + [{:service "foursquare", + :foursquare-photo-id "bb3679fc-46fa-4b28-b0d2-291b301c67c1", + :mayor "rasta_toucan"} + "Pacific Heights Free-Range Eatery is a swell and swell place to have a birthday party the first Sunday of the month." + {:small + "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/small.jpg", + :medium + "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/med.jpg", + :large + "http://cloudfront.net/52083005-7074-4b91-b2e7-aa3cbd81bb21/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "foursquare", + :foursquare-photo-id "76baeb06-f79c-43c4-ae15-108b69983aa3", + :mayor "mandy"} + "Mission Homestyle Churros is a swell and great place to have a birthday party weekend evenings." + {:small + "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/small.jpg", + :medium + "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/med.jpg", + :large + "http://cloudfront.net/bd9023b0-0eda-41d6-823c-9cf526e5f5f3/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "yelp", + :yelp-photo-id "35f2e21d-b05f-4e7c-adfe-e8d70ecd478e", + :categories ["Japanese" "Coffee House"]} + "Mission Japanese Coffee House is a classic and wonderful place to watch the Warriors game in the spring." + {:small + "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/small.jpg", + :medium + "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/med.jpg", + :large + "http://cloudfront.net/8bff3709-0af5-4e6f-9963-575cb5044b37/large.jpg"} + {:name "Mission Japanese Coffee House", + :categories ["Japanese" "Coffee House"], + :phone "415-561-0506", + :id "60dd274e-0cbf-4521-946d-8a4e0f151150"}] + [{:service "yelp", + :yelp-photo-id "f1bc8db8-9cd5-4664-b6d9-440618790ffc", + :categories ["Free-Range" "Taqueria"]} + "Kyle's Free-Range Taqueria is a popular and modern place to have a birthday party in June." + {:small + "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/small.jpg", + :medium + "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/med.jpg", + :large + "http://cloudfront.net/00ba782a-b50a-4246-88eb-c965f39a27b2/large.jpg"} + {:name "Kyle's Free-Range Taqueria", + :categories ["Free-Range" "Taqueria"], + :phone "415-201-7832", + :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"}] + [{:service "flare", :username "mandy"} + "Haight Soul Food Hotel & Restaurant is a wonderful and amazing place to meet new friends with friends." + {:small + "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/small.jpg", + :medium + "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/med.jpg", + :large + "http://cloudfront.net/84c1175b-e3aa-47cd-83b8-abf1122496bf/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "twitter", + :mentions ["@soma_japanese_churros"], + :tags ["#japanese" "#churros"], + :username "tupac"} + "SoMa Japanese Churros is a fantastic and world-famous place to watch the Warriors game when hungover." + {:small + "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/small.jpg", + :medium + "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/med.jpg", + :large + "http://cloudfront.net/60f59426-c2db-4bb5-885b-af2641a8f7b5/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "yelp", + :yelp-photo-id "da8703eb-dd7c-4559-b39c-e2a6fff91b92", + :categories ["Pizza" "Bakery"]} + "Pacific Heights Pizza Bakery is a overrated and exclusive place to take visiting friends and relatives on public holidays." + {:small + "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/small.jpg", + :medium + "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/med.jpg", + :large + "http://cloudfront.net/721c2488-5e3b-4f25-9da4-463c5cbecf21/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "twitter", + :mentions ["@mission_british_café"], + :tags ["#british" "#café"], + :username "lucky_pigeon"} + "Mission British Café is a decent and swell place to drink a craft beer in June." + {:small + "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/small.jpg", + :medium + "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/med.jpg", + :large + "http://cloudfront.net/75976ced-01ca-4762-93aa-3e9b255a8dc7/large.jpg"} + {:name "Mission British Café", + :categories ["British" "Café"], + :phone "415-715-7004", + :id "c99899e3-439c-4444-9dc4-5598632aec8d"}] + [{:service "flare", :username "mandy"} + "Oakland American Grill is a amazing and underground place to have brunch on Saturday night." + {:small + "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/small.jpg", + :medium + "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/med.jpg", + :large + "http://cloudfront.net/a848ecf7-8d6a-4bb4-bd15-674fac1b7f79/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "twitter", + :mentions ["@pacific_heights_free_range_eatery"], + :tags ["#free-range" "#eatery"], + :username "biggie"} + "Pacific Heights Free-Range Eatery is a great and modern place to take visiting friends and relatives on a Tuesday afternoon." + {:small + "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/small.jpg", + :medium + "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/med.jpg", + :large + "http://cloudfront.net/b12a9c02-b78a-43de-a4e4-838be542a7b7/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "flare", :username "joe"} + "SoMa Old-Fashioned Pizzeria is a groovy and delicious place to nurse a hangover when hungover." + {:small + "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/small.jpg", + :medium + "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/med.jpg", + :large + "http://cloudfront.net/1182d679-c0cb-4250-b219-d6da0fefaa2e/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "twitter", + :mentions ["@haight_mexican_restaurant"], + :tags ["#mexican" "#restaurant"], + :username "amy"} + "Haight Mexican Restaurant is a historical and horrible place to sip Champagne weekday afternoons." + {:small + "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/small.jpg", + :medium + "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/med.jpg", + :large + "http://cloudfront.net/18129bb1-88f0-45c2-ba6b-bb86b8004a18/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "twitter", + :mentions ["@nob_hill_korean_taqueria"], + :tags ["#korean" "#taqueria"], + :username "jessica"} + "Nob Hill Korean Taqueria is a overrated and classic place to pitch an investor weekend mornings." + {:small + "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/small.jpg", + :medium + "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/med.jpg", + :large + "http://cloudfront.net/c466065c-7be8-46d0-8920-f0a98d4d94ef/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "foursquare", + :foursquare-photo-id "e00287d6-3633-42a1-a096-7756abdc25fa", + :mayor "joe"} + "Joe's Modern Coffee House is a acceptable and fantastic place to watch the Giants game in the spring." + {:small + "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/small.jpg", + :medium + "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/med.jpg", + :large + "http://cloudfront.net/ec13291b-2bee-4993-8198-0c1f799f9a3b/large.jpg"} + {:name "Joe's Modern Coffee House", + :categories ["Modern" "Coffee House"], + :phone "415-331-5269", + :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"}] + [{:service "twitter", + :mentions ["@kyles_chinese_restaurant"], + :tags ["#chinese" "#restaurant"], + :username "jessica"} + "Kyle's Chinese Restaurant is a exclusive and great place to have a after-work cocktail on Saturday night." + {:small + "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/small.jpg", + :medium + "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/med.jpg", + :large + "http://cloudfront.net/bc41c4ff-50e6-4971-9de8-14cd0ed203ee/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "facebook", + :facebook-photo-id "a909c6c2-faee-4994-9f99-a922f40d64ea", + :url + "http://facebook.com/photos/a909c6c2-faee-4994-9f99-a922f40d64ea"} + "Pacific Heights Soul Food Coffee House is a fantastic and great place to catch a bite to eat in June." + {:small + "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/small.jpg", + :medium + "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/med.jpg", + :large + "http://cloudfront.net/b00518e6-988c-4175-a17a-e49b5924a014/large.jpg"} + {:name "Pacific Heights Soul Food Coffee House", + :categories ["Soul Food" "Coffee House"], + :phone "415-838-3464", + :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"}] + [{:service "twitter", + :mentions ["@chinatown_paleo_food_truck"], + :tags ["#paleo" "#food" "#truck"], + :username "joe"} + "Chinatown Paleo Food Truck is a underground and well-decorated place to drink a craft beer weekend evenings." + {:small + "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/small.jpg", + :medium + "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/med.jpg", + :large + "http://cloudfront.net/ffa9dc6d-5d1c-4475-923c-72934f3e8762/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "flare", :username "tupac"} + "Lucky's Low-Carb Coffee House is a delicious and atmospheric place to take a date during winter." + {:small + "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/small.jpg", + :medium + "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/med.jpg", + :large + "http://cloudfront.net/256793eb-de7f-45c6-9143-9fde81134caf/large.jpg"} + {:name "Lucky's Low-Carb Coffee House", + :categories ["Low-Carb" "Coffee House"], + :phone "415-145-7107", + :id "81b0f944-f0ce-45e5-b84e-a924c441064a"}] + [{:service "facebook", + :facebook-photo-id "33e0c40f-43b2-4f59-8539-9ff952ce06c3", + :url + "http://facebook.com/photos/33e0c40f-43b2-4f59-8539-9ff952ce06c3"} + "Sunset American Churros is a underground and acceptable place to have a drink in the spring." + {:small + "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/small.jpg", + :medium + "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/med.jpg", + :large + "http://cloudfront.net/d138162c-e3bc-4d50-b3bd-55bc9b599e9a/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "facebook", + :facebook-photo-id "36ecd1a2-0282-422c-995a-664668a4cb80", + :url + "http://facebook.com/photos/36ecd1a2-0282-422c-995a-664668a4cb80"} + "Kyle's Chinese Restaurant is a historical and atmospheric place to watch the Warriors game in June." + {:small + "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/small.jpg", + :medium + "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/med.jpg", + :large + "http://cloudfront.net/267696df-7262-4dab-9c71-0346861f9a9c/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "flare", :username "amy"} + "Polk St. Korean Taqueria is a overrated and popular place to watch the Warriors game with your pet dog." + {:small + "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/small.jpg", + :medium + "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/med.jpg", + :large + "http://cloudfront.net/a4d216fa-08b5-4f73-a236-eabf0b3e38e7/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "twitter", + :mentions ["@marina_japanese_liquor_store"], + :tags ["#japanese" "#liquor" "#store"], + :username "jane"} + "Marina Japanese Liquor Store is a underappreciated and fantastic place to conduct a business meeting in June." + {:small + "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/small.jpg", + :medium + "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/med.jpg", + :large + "http://cloudfront.net/cd9e419f-75ef-404b-8637-7120d469b743/large.jpg"} + {:name "Marina Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-587-9819", + :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"}] + [{:service "facebook", + :facebook-photo-id "54f31306-b4ce-46f6-b30e-37dc2f10fc18", + :url + "http://facebook.com/photos/54f31306-b4ce-46f6-b30e-37dc2f10fc18"} + "Polk St. Red White & Blue Café is a underground and horrible place to nurse a hangover on Taco Tuesday." + {:small + "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/small.jpg", + :medium + "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/med.jpg", + :large + "http://cloudfront.net/9629743a-e19f-4443-966e-b3cede3cce45/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "yelp", + :yelp-photo-id "c8f35c36-0f19-4318-a0e4-88d96b5b72eb", + :categories ["Old-Fashioned" "Pizzeria"]} + "SoMa Old-Fashioned Pizzeria is a groovy and amazing place to have brunch with your pet toucan." + {:small + "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/small.jpg", + :medium + "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/med.jpg", + :large + "http://cloudfront.net/a29570eb-e53f-4ddd-ab61-747cf6709515/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "yelp", + :yelp-photo-id "d753f0de-f1db-4cf0-9260-ad3eeee4aa9c", + :categories ["Soul Food" "Hotel & Restaurant"]} + "Haight Soul Food Hotel & Restaurant is a popular and modern place to take visiting friends and relatives weekday afternoons." + {:small + "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/small.jpg", + :medium + "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/med.jpg", + :large + "http://cloudfront.net/a0842810-2d69-48c3-ba37-941479473250/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "foursquare", + :foursquare-photo-id "a5c87b6a-a1e6-4abe-932f-90dde0e8125b", + :mayor "tupac"} + "Marina Low-Carb Food Truck is a underappreciated and modern place to take a date with friends." + {:small + "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/small.jpg", + :medium + "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/med.jpg", + :large + "http://cloudfront.net/576cf625-4a9e-443a-b0df-ffb279f418f8/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "yelp", + :yelp-photo-id "728c253f-8576-4e91-a0f7-8f4409ef3ea3", + :categories ["Soul Food" "Pizzeria"]} + "Mission Soul Food Pizzeria is a amazing and world-famous place to have a after-work cocktail in the spring." + {:small + "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/small.jpg", + :medium + "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/med.jpg", + :large + "http://cloudfront.net/171695dd-4ad1-4c50-9056-27d6d80d524a/large.jpg"} + {:name "Mission Soul Food Pizzeria", + :categories ["Soul Food" "Pizzeria"], + :phone "415-437-3479", + :id "9905fe61-44cb-4626-843b-5d725c7949bb"}] + [{:service "twitter", + :mentions ["@cams_old_fashioned_coffee_house"], + :tags ["#old-fashioned" "#coffee" "#house"], + :username "tupac"} + "Cam's Old-Fashioned Coffee House is a delicious and overrated place to have a birthday party weekend mornings." + {:small + "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/small.jpg", + :medium + "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/med.jpg", + :large + "http://cloudfront.net/8270c1f4-e118-4d40-a5f1-f2257c99b3ab/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-871-9473", + :id "5d1f918e-ef00-40de-b6e4-3f9f34ee8cd4"}] + [{:service "twitter", + :mentions ["@haight_european_grill"], + :tags ["#european" "#grill"], + :username "lucky_pigeon"} + "Haight European Grill is a family-friendly and wonderful place to sip Champagne on Saturday night." + {:small + "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/small.jpg", + :medium + "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/med.jpg", + :large + "http://cloudfront.net/1b7745d8-0082-495f-a767-589bfd31dc04/large.jpg"} + {:name "Haight European Grill", + :categories ["European" "Grill"], + :phone "415-191-2778", + :id "7e6281f7-5b17-4056-ada0-85453247bc8f"}] + [{:service "flare", :username "mandy"} + "Polk St. Deep-Dish Hotel & Restaurant is a world-famous and popular place to sip Champagne with your pet dog." + {:small + "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/small.jpg", + :medium + "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/med.jpg", + :large + "http://cloudfront.net/d81ae9d6-676c-45c5-add1-f0c007db6de5/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "twitter", + :mentions ["@sf_afgan_restaurant"], + :tags ["#afgan" "#restaurant"], + :username "rasta_toucan"} + "SF Afgan Restaurant is a horrible and delicious place to sip a glass of expensive wine the first Sunday of the month." + {:small + "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/small.jpg", + :medium + "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/med.jpg", + :large + "http://cloudfront.net/77737d07-f201-4898-90b9-72b7d4c73de9/large.jpg"} + {:name "SF Afgan Restaurant", + :categories ["Afgan" "Restaurant"], + :phone "415-451-4697", + :id "66ccc68a-db9a-470c-a17b-7764d23daced"}] + [{:service "facebook", + :facebook-photo-id "f073cb47-002c-4db7-b80a-c5ed327d0ac9", + :url + "http://facebook.com/photos/f073cb47-002c-4db7-b80a-c5ed327d0ac9"} + "Tenderloin Gluten-Free Bar & Grill is a exclusive and wonderful place to meet new friends on Thursdays." + {:small + "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/small.jpg", + :medium + "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/med.jpg", + :large + "http://cloudfront.net/48526d8c-934d-4f24-b31e-ce1bc15bc734/large.jpg"} + {:name "Tenderloin Gluten-Free Bar & Grill", + :categories ["Gluten-Free" "Bar & Grill"], + :phone "415-904-0956", + :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"}] + [{:service "flare", :username "lucky_pigeon"} + "Pacific Heights Irish Grill is a overrated and popular place to catch a bite to eat during winter." + {:small + "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/small.jpg", + :medium + "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/med.jpg", + :large + "http://cloudfront.net/c01dad01-edba-4413-b723-52d87a587f2d/large.jpg"} + {:name "Pacific Heights Irish Grill", + :categories ["Irish" "Grill"], + :phone "415-491-2202", + :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"}] + [{:service "flare", :username "jane"} + "Haight Soul Food Café is a horrible and underground place to take visiting friends and relatives on Thursdays." + {:small + "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/small.jpg", + :medium + "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/med.jpg", + :large + "http://cloudfront.net/f3bd2564-9a67-4712-86e7-5c1abe816bd0/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "twitter", + :mentions ["@marina_cage_free_liquor_store"], + :tags ["#cage-free" "#liquor" "#store"], + :username "bob"} + "Marina Cage-Free Liquor Store is a delicious and family-friendly place to watch the Warriors game after baseball games." + {:small + "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/small.jpg", + :medium + "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/med.jpg", + :large + "http://cloudfront.net/ea520d10-a490-416c-a603-058a212a3e0c/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "flare", :username "rasta_toucan"} + "Lower Pac Heights Deep-Dish Ice Cream Truck is a classic and underground place to take a date with friends." + {:small + "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/small.jpg", + :medium + "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/med.jpg", + :large + "http://cloudfront.net/6a960d66-43a5-4053-b5d6-b03ab59a263b/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", + :categories ["Deep-Dish" "Ice Cream Truck"], + :phone "415-495-1414", + :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"}] + [{:service "twitter", + :mentions ["@soma_old_fashioned_pizzeria"], + :tags ["#old-fashioned" "#pizzeria"], + :username "biggie"} + "SoMa Old-Fashioned Pizzeria is a swell and acceptable place to sip a glass of expensive wine on a Tuesday afternoon." + {:small + "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/small.jpg", + :medium + "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/med.jpg", + :large + "http://cloudfront.net/a04b6844-2caf-4303-9921-e750208f79ee/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "facebook", + :facebook-photo-id "98c2e661-c20e-41df-967a-b65635e34031", + :url + "http://facebook.com/photos/98c2e661-c20e-41df-967a-b65635e34031"} + "SF British Pop-Up Food Stand is a swell and popular place to people-watch on Thursdays." + {:small + "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/small.jpg", + :medium + "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/med.jpg", + :large + "http://cloudfront.net/fc488143-5e29-44be-9dc4-2b7a3653c3b3/large.jpg"} + {:name "SF British Pop-Up Food Stand", + :categories ["British" "Pop-Up Food Stand"], + :phone "415-441-3725", + :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"}] + [{:service "flare", :username "rasta_toucan"} + "Lower Pac Heights Cage-Free Coffee House is a classic and fantastic place to take visiting friends and relatives with your pet dog." + {:small + "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/small.jpg", + :medium + "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/med.jpg", + :large + "http://cloudfront.net/9b7e4812-949c-4b68-8ff5-312e6bf143fb/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "twitter", + :mentions ["@haight_mexican_restaurant"], + :tags ["#mexican" "#restaurant"], + :username "rasta_toucan"} + "Haight Mexican Restaurant is a amazing and underappreciated place to nurse a hangover weekday afternoons." + {:small + "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/small.jpg", + :medium + "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/med.jpg", + :large + "http://cloudfront.net/df889bbe-152e-483f-9599-a6e12ae821a7/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "facebook", + :facebook-photo-id "81a3711b-d0c6-4100-b3f7-b18f67613a09", + :url + "http://facebook.com/photos/81a3711b-d0c6-4100-b3f7-b18f67613a09"} + "Cam's Old-Fashioned Coffee House is a delicious and great place to pitch an investor when hungover." + {:small + "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/small.jpg", + :medium + "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/med.jpg", + :large + "http://cloudfront.net/80c44503-734b-4aac-8659-1d90ddd579ab/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-868-2973", + :id "27592c2b-e682-44bb-be28-8e9a622becca"}] + [{:service "flare", :username "joe"} + "Tenderloin Gormet Restaurant is a modern and decent place to have a after-work cocktail during winter." + {:small + "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/small.jpg", + :medium + "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/med.jpg", + :large + "http://cloudfront.net/cf1833eb-476c-45fc-8eb5-68fd009f0871/large.jpg"} + {:name "Tenderloin Gormet Restaurant", + :categories ["Gormet" "Restaurant"], + :phone "415-127-4197", + :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"}] + [{:service "twitter", + :mentions ["@chinatown_paleo_food_truck"], + :tags ["#paleo" "#food" "#truck"], + :username "joe"} + "Chinatown Paleo Food Truck is a classic and underground place to watch the Giants game in the fall." + {:small + "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/small.jpg", + :medium + "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/med.jpg", + :large + "http://cloudfront.net/9432fe46-24be-40e9-a0b5-8824149712fd/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "twitter", + :mentions ["@haight_soul_food_hotel_&_restaurant"], + :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], + :username "bob"} + "Haight Soul Food Hotel & Restaurant is a family-friendly and amazing place to watch the Warriors game in June." + {:small + "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/small.jpg", + :medium + "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/med.jpg", + :large + "http://cloudfront.net/2fff12e5-6582-4c35-8fed-4bae3f61acc1/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "flare", :username "kyle"} + "Haight Soul Food Pop-Up Food Stand is a decent and underground place to conduct a business meeting in the spring." + {:small + "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/small.jpg", + :medium + "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/med.jpg", + :large + "http://cloudfront.net/6509339f-e90f-4961-9041-25984c0068e1/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "foursquare", + :foursquare-photo-id "7d0faa6d-0d94-4920-894a-8da7537839a7", + :mayor "bob"} + "Joe's Modern Coffee House is a underappreciated and delicious place to take a date in July." + {:small + "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/small.jpg", + :medium + "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/med.jpg", + :large + "http://cloudfront.net/1402d2a6-e94f-43dc-b66c-22621bfc0709/large.jpg"} + {:name "Joe's Modern Coffee House", + :categories ["Modern" "Coffee House"], + :phone "415-331-5269", + :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"}] + [{:service "flare", :username "biggie"} + "Polk St. Mexican Coffee House is a popular and historical place to catch a bite to eat with your pet toucan." + {:small + "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/small.jpg", + :medium + "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/med.jpg", + :large + "http://cloudfront.net/a8c99e30-dc02-456c-b752-91702acb84c5/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "twitter", + :mentions ["@marina_no_msg_sushi"], + :tags ["#no-msg" "#sushi"], + :username "rasta_toucan"} + "Marina No-MSG Sushi is a overrated and overrated place to pitch an investor Friday nights." + {:small + "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/small.jpg", + :medium + "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/med.jpg", + :large + "http://cloudfront.net/c14d1b58-b607-4a3b-86c0-331e6b965534/large.jpg"} + {:name "Marina No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-856-5937", + :id "d51013a3-8547-4705-a5f0-cb11d8206481"}] + [{:service "twitter", + :mentions ["@sunset_deep_dish_hotel_&_restaurant"], + :tags ["#deep-dish" "#hotel" "#&" "#restaurant"], + :username "rasta_toucan"} + "Sunset Deep-Dish Hotel & Restaurant is a horrible and world-famous place to catch a bite to eat Friday nights." + {:small + "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/small.jpg", + :medium + "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/med.jpg", + :large + "http://cloudfront.net/038a5a74-18a8-48f2-a771-6a32c9c57d98/large.jpg"} + {:name "Sunset Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-332-0978", + :id "a80745c7-af74-4579-8932-70dd488269e6"}] + [{:service "twitter", + :mentions ["@pacific_heights_free_range_eatery"], + :tags ["#free-range" "#eatery"], + :username "kyle"} + "Pacific Heights Free-Range Eatery is a wonderful and modern place to take visiting friends and relatives Friday nights." + {:small + "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/small.jpg", + :medium + "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/med.jpg", + :large + "http://cloudfront.net/cedd4221-dbdb-46c3-95a9-935cce6b3fe5/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "facebook", + :facebook-photo-id "dfad685c-f2a7-4ab4-ba45-a9d94919d8f6", + :url + "http://facebook.com/photos/dfad685c-f2a7-4ab4-ba45-a9d94919d8f6"} + "Lucky's Deep-Dish Gastro Pub is a decent and delicious place to watch the Giants game on Taco Tuesday." + {:small + "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/small.jpg", + :medium + "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/med.jpg", + :large + "http://cloudfront.net/a9b4f7f8-637b-4b83-9881-78d3b7f417dc/large.jpg"} + {:name "Lucky's Deep-Dish Gastro Pub", + :categories ["Deep-Dish" "Gastro Pub"], + :phone "415-487-4085", + :id "0136c454-0968-41cd-a237-ceec5724cab8"}] + [{:service "twitter", + :mentions ["@polk_st._mexican_coffee_house"], + :tags ["#mexican" "#coffee" "#house"], + :username "bob"} + "Polk St. Mexican Coffee House is a world-famous and horrible place to pitch an investor the second Saturday of the month." + {:small + "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/small.jpg", + :medium + "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/med.jpg", + :large + "http://cloudfront.net/cf7ce0ef-0ce6-4138-9f33-a13de2a6c752/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "yelp", + :yelp-photo-id "a7da25d3-cf05-444f-9a1c-11541fdbdb78", + :categories ["Pizza" "Bakery"]} + "Pacific Heights Pizza Bakery is a acceptable and fantastic place to have a after-work cocktail after baseball games." + {:small + "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/small.jpg", + :medium + "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/med.jpg", + :large + "http://cloudfront.net/71ec461a-21c1-4c41-9fe5-31e69dfd95f4/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "flare", :username "lucky_pigeon"} + "SoMa Old-Fashioned Pizzeria is a underappreciated and groovy place to take a date with your pet dog." + {:small + "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/small.jpg", + :medium + "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/med.jpg", + :large + "http://cloudfront.net/03e3ec21-9842-4b57-a311-3c1ecf5716c3/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "yelp", + :yelp-photo-id "a031364b-8035-45ba-8948-3e3d42cd0bb1", + :categories ["Homestyle" "Pop-Up Food Stand"]} + "Marina Homestyle Pop-Up Food Stand is a amazing and acceptable place to take visiting friends and relatives during winter." + {:small + "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/small.jpg", + :medium + "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/med.jpg", + :large + "http://cloudfront.net/2da70d7a-afe8-4708-84a4-8023b813194d/large.jpg"} + {:name "Marina Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-094-4567", + :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"}] + [{:service "twitter", + :mentions ["@haight_european_grill"], + :tags ["#european" "#grill"], + :username "kyle"} + "Haight European Grill is a horrible and amazing place to have a birthday party during winter." + {:small + "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/small.jpg", + :medium + "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/med.jpg", + :large + "http://cloudfront.net/1dcef7de-a1c4-405b-a9e1-69c92d686ef1/large.jpg"} + {:name "Haight European Grill", + :categories ["European" "Grill"], + :phone "415-191-2778", + :id "7e6281f7-5b17-4056-ada0-85453247bc8f"}] + [{:service "facebook", + :facebook-photo-id "d9c67f4f-f651-4e29-91bf-fe8a13c51a42", + :url + "http://facebook.com/photos/d9c67f4f-f651-4e29-91bf-fe8a13c51a42"} + "SoMa Japanese Churros is a horrible and overrated place to people-watch during winter." + {:small + "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/small.jpg", + :medium + "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/med.jpg", + :large + "http://cloudfront.net/f49ffbdc-9f8b-4191-ad5c-a6d32a709fec/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "yelp", + :yelp-photo-id "a09b2b6a-2cc4-4c75-ad94-bc4b255f2609", + :categories ["Japanese" "Liquor Store"]} + "Marina Japanese Liquor Store is a wonderful and historical place to people-watch in July." + {:small + "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/small.jpg", + :medium + "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/med.jpg", + :large + "http://cloudfront.net/b6595a58-aa5a-4653-a582-321a0499af2c/large.jpg"} + {:name "Marina Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-587-9819", + :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"}] + [{:service "foursquare", + :foursquare-photo-id "e11de3d3-8166-424c-91a4-857d3abb8487", + :mayor "kyle"} + "Nob Hill Gluten-Free Coffee House is a popular and historical place to take visiting friends and relatives weekend mornings." + {:small + "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/small.jpg", + :medium + "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/med.jpg", + :large + "http://cloudfront.net/f42279fb-7c4d-4dc6-8788-04be94c68b67/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "yelp", + :yelp-photo-id "101ea39b-5bc5-4bb7-905e-017f1a8271c8", + :categories ["Deep-Dish" "Hotel & Restaurant"]} + "Polk St. Deep-Dish Hotel & Restaurant is a family-friendly and amazing place to take visiting friends and relatives in the spring." + {:small + "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/small.jpg", + :medium + "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/med.jpg", + :large + "http://cloudfront.net/a938a596-27ad-4d83-bc32-d5113d44bc56/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "flare", :username "sameer"} + "Joe's No-MSG Sushi is a underappreciated and family-friendly place to people-watch weekend evenings." + {:small + "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/small.jpg", + :medium + "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/med.jpg", + :large + "http://cloudfront.net/5469697b-2b07-46ab-b9ef-3bfa449e7db5/large.jpg"} + {:name "Joe's No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-739-8157", + :id "9ff21570-cd5b-415e-933a-52144f551b86"}] + [{:service "yelp", + :yelp-photo-id "22c576f1-d64a-44c1-a510-85ed03cd8a9c", + :categories ["Soul Food" "Pizzeria"]} + "Mission Soul Food Pizzeria is a acceptable and historical place to pitch an investor during winter." + {:small + "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/small.jpg", + :medium + "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/med.jpg", + :large + "http://cloudfront.net/38325411-8a54-4a10-930f-0c1a439d0f78/large.jpg"} + {:name "Mission Soul Food Pizzeria", + :categories ["Soul Food" "Pizzeria"], + :phone "415-437-3479", + :id "9905fe61-44cb-4626-843b-5d725c7949bb"}] + [{:service "facebook", + :facebook-photo-id "3e5b3749-a758-4acf-b87e-aadca225127c", + :url + "http://facebook.com/photos/3e5b3749-a758-4acf-b87e-aadca225127c"} + "Joe's Homestyle Eatery is a popular and underappreciated place to drink a craft beer with your pet toucan." + {:small + "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/small.jpg", + :medium + "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/med.jpg", + :large + "http://cloudfront.net/9d0c77a6-ac3a-4221-8125-c670c48a963a/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "flare", :username "amy"} + "Market St. Gluten-Free Café is a family-friendly and family-friendly place to have a after-work cocktail when hungover." + {:small + "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/small.jpg", + :medium + "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/med.jpg", + :large + "http://cloudfront.net/f1891933-8d88-4e80-92c3-76b0a10c6b45/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "twitter", + :mentions ["@tenderloin_cage_free_sushi"], + :tags ["#cage-free" "#sushi"], + :username "joe"} + "Tenderloin Cage-Free Sushi is a swell and classic place to nurse a hangover with your pet toucan." + {:small + "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/small.jpg", + :medium + "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/med.jpg", + :large + "http://cloudfront.net/325091a5-4a50-45bd-9566-654940a8932c/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "twitter", + :mentions ["@mission_chinese_liquor_store"], + :tags ["#chinese" "#liquor" "#store"], + :username "jane"} + "Mission Chinese Liquor Store is a family-friendly and great place to take visiting friends and relatives weekday afternoons." + {:small + "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/small.jpg", + :medium + "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/med.jpg", + :large + "http://cloudfront.net/1b313721-9de8-4f91-afe2-659873693387/large.jpg"} + {:name "Mission Chinese Liquor Store", + :categories ["Chinese" "Liquor Store"], + :phone "415-906-6919", + :id "00132b5b-31fc-46f0-a288-f547f23477ee"}] + [{:service "foursquare", + :foursquare-photo-id "f7b66a97-8c94-4754-8938-6b4e3244b08e", + :mayor "jane"} + "Lucky's Low-Carb Coffee House is a great and decent place to conduct a business meeting when hungover." + {:small + "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/small.jpg", + :medium + "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/med.jpg", + :large + "http://cloudfront.net/e9e3efe7-1b9a-48c7-85d6-92a1322960b8/large.jpg"} + {:name "Lucky's Low-Carb Coffee House", + :categories ["Low-Carb" "Coffee House"], + :phone "415-145-7107", + :id "81b0f944-f0ce-45e5-b84e-a924c441064a"}] + [{:service "flare", :username "jane"} + "Lower Pac Heights Cage-Free Coffee House is a exclusive and fantastic place to take visiting friends and relatives with your pet dog." + {:small + "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/small.jpg", + :medium + "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/med.jpg", + :large + "http://cloudfront.net/b3321e36-0ffa-40be-bba4-f8ada008c0f0/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "yelp", + :yelp-photo-id "cf483e10-dd11-4611-90e0-bc0238b41b59", + :categories ["TaquerÃa" "Diner"]} + "SoMa TaquerÃa Diner is a overrated and amazing place to have breakfast in June." + {:small + "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/small.jpg", + :medium + "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/med.jpg", + :large + "http://cloudfront.net/36b82a08-66c3-4c94-a76d-0026653fadf0/large.jpg"} + {:name "SoMa TaquerÃa Diner", + :categories ["TaquerÃa" "Diner"], + :phone "415-947-9521", + :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"}] + [{:service "yelp", + :yelp-photo-id "33a70c42-6c5a-4a29-af94-b7856cbc6cbb", + :categories ["Mexican" "Gastro Pub"]} + "Cam's Mexican Gastro Pub is a swell and world-famous place to take visiting friends and relatives Friday nights." + {:small + "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/small.jpg", + :medium + "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/med.jpg", + :large + "http://cloudfront.net/c4414384-985d-4539-b6e5-1758bd4ec73a/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "yelp", + :yelp-photo-id "e6a2e47f-07b6-4ec9-a491-371fb1959975", + :categories ["Pizza" "Churros"]} + "Alcatraz Pizza Churros is a horrible and underground place to sip a glass of expensive wine during winter." + {:small + "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/small.jpg", + :medium + "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/med.jpg", + :large + "http://cloudfront.net/a0d56ff5-9a10-4b98-bdcf-152d45019943/large.jpg"} + {:name "Alcatraz Pizza Churros", + :categories ["Pizza" "Churros"], + :phone "415-754-7867", + :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"}] + [{:service "flare", :username "tupac"} + "Marina Low-Carb Food Truck is a groovy and delicious place to watch the Giants game on Thursdays." + {:small + "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/small.jpg", + :medium + "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/med.jpg", + :large + "http://cloudfront.net/91c8de79-39fa-41b8-8022-fdfef267bb71/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "facebook", + :facebook-photo-id "0b3c791c-935c-4d01-84f8-9708c699eb1b", + :url + "http://facebook.com/photos/0b3c791c-935c-4d01-84f8-9708c699eb1b"} + "Rasta's Paleo Churros is a acceptable and family-friendly place to have brunch weekday afternoons." + {:small + "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/small.jpg", + :medium + "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/med.jpg", + :large + "http://cloudfront.net/b3866479-4dfb-48e8-97fa-058d125e36e7/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "yelp", + :yelp-photo-id "c6aa321a-e8f7-484b-9fd1-ab8f6a1c5906", + :categories ["Red White & Blue" "Pizzeria"]} + "Tenderloin Red White & Blue Pizzeria is a swell and historical place to nurse a hangover Friday nights." + {:small + "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/small.jpg", + :medium + "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/med.jpg", + :large + "http://cloudfront.net/99dce602-9ba9-4a3c-b9c3-c86ef6f9bcfa/large.jpg"} + {:name "Tenderloin Red White & Blue Pizzeria", + :categories ["Red White & Blue" "Pizzeria"], + :phone "415-719-8143", + :id "eba3dbcd-100a-4f38-a701-e0dec157f437"}] + [{:service "flare", :username "amy"} + "Kyle's Free-Range Taqueria is a amazing and wonderful place to have breakfast in the fall." + {:small + "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/small.jpg", + :medium + "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/med.jpg", + :large + "http://cloudfront.net/613f6695-adee-4175-ad07-1af6b7753fa7/large.jpg"} + {:name "Kyle's Free-Range Taqueria", + :categories ["Free-Range" "Taqueria"], + :phone "415-201-7832", + :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"}] + [{:service "foursquare", + :foursquare-photo-id "ef083c69-08d6-45be-8cfb-c71654b0a8fc", + :mayor "cam_saul"} + "Lucky's Cage-Free Liquor Store is a delicious and acceptable place to drink a craft beer on public holidays." + {:small + "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/small.jpg", + :medium + "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/med.jpg", + :large + "http://cloudfront.net/9311a1df-401c-4c89-b4ee-d399635fd558/large.jpg"} + {:name "Lucky's Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-341-3219", + :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"}] + [{:service "twitter", + :mentions ["@luckys_gluten_free_café"], + :tags ["#gluten-free" "#café"], + :username "biggie"} + "Lucky's Gluten-Free Café is a groovy and wonderful place to have a drink with friends." + {:small + "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/small.jpg", + :medium + "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/med.jpg", + :large + "http://cloudfront.net/85d2ae70-0381-4c67-9d5f-97f8068e64df/large.jpg"} + {:name "Lucky's Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-740-2328", + :id "379af987-ad40-4a93-88a6-0233e1c14649"}] + [{:service "flare", :username "kyle"} + "Marina Modern Sushi is a wonderful and exclusive place to watch the Warriors game the second Saturday of the month." + {:small + "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/small.jpg", + :medium + "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/med.jpg", + :large + "http://cloudfront.net/ad6ceaf6-a43a-4c35-8f6c-ecef429c7408/large.jpg"} + {:name "Marina Modern Sushi", + :categories ["Modern" "Sushi"], + :phone "415-393-7672", + :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"}] + [{:service "foursquare", + :foursquare-photo-id "55c948c7-8d08-46e1-98e6-a03ca330795c", + :mayor "mandy"} + "Pacific Heights Red White & Blue Bar & Grill is a decent and delicious place to watch the Giants game when hungover." + {:small + "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/small.jpg", + :medium + "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/med.jpg", + :large + "http://cloudfront.net/7ae8b782-4cf9-4efd-af2f-12f3d921e44a/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "flare", :username "kyle"} + "Joe's Modern Coffee House is a acceptable and exclusive place to catch a bite to eat during summer." + {:small + "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/small.jpg", + :medium + "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/med.jpg", + :large + "http://cloudfront.net/9f063ae2-9db6-490e-8098-bac5a030b5d4/large.jpg"} + {:name "Joe's Modern Coffee House", + :categories ["Modern" "Coffee House"], + :phone "415-331-5269", + :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"}] + [{:service "flare", :username "cam_saul"} + "Haight Gormet Pizzeria is a historical and modern place to pitch an investor Friday nights." + {:small + "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/small.jpg", + :medium + "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/med.jpg", + :large + "http://cloudfront.net/1484e4b7-fe56-4bed-8598-c6558202adeb/large.jpg"} + {:name "Haight Gormet Pizzeria", + :categories ["Gormet" "Pizzeria"], + :phone "415-869-2197", + :id "0425bdd0-3f57-4108-80e3-78335327355a"}] + [{:service "yelp", + :yelp-photo-id "c76eeafe-a161-492d-8a84-f0d3eb729d2b", + :categories ["Mexican" "Gastro Pub"]} + "Cam's Mexican Gastro Pub is a world-famous and fantastic place to take a date in July." + {:small + "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/small.jpg", + :medium + "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/med.jpg", + :large + "http://cloudfront.net/7663dd1e-d8d3-4add-9cdd-e6b7322e622c/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "flare", :username "joe"} + "Haight European Grill is a acceptable and underground place to have breakfast on a Tuesday afternoon." + {:small + "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/small.jpg", + :medium + "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/med.jpg", + :large + "http://cloudfront.net/833021dd-cdcf-419f-8891-cf680f8d124e/large.jpg"} + {:name "Haight European Grill", + :categories ["European" "Grill"], + :phone "415-191-2778", + :id "7e6281f7-5b17-4056-ada0-85453247bc8f"}] + [{:service "facebook", + :facebook-photo-id "fd4144e1-bddb-4f4d-8985-de94a554a730", + :url + "http://facebook.com/photos/fd4144e1-bddb-4f4d-8985-de94a554a730"} + "Tenderloin Cage-Free Sushi is a modern and atmospheric place to sip Champagne weekday afternoons." + {:small + "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/small.jpg", + :medium + "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/med.jpg", + :large + "http://cloudfront.net/4e14334f-fa3d-4c90-af94-7aa9c37a5dbd/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "yelp", + :yelp-photo-id "50bdc257-fcc9-44d1-b121-87f5c3e45058", + :categories ["Deep-Dish" "Liquor Store"]} + "Lower Pac Heights Deep-Dish Liquor Store is a horrible and decent place to pitch an investor on Taco Tuesday." + {:small + "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/small.jpg", + :medium + "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/med.jpg", + :large + "http://cloudfront.net/3d883dfb-23a7-4097-aed9-d69c4cbb67ef/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Liquor Store", + :categories ["Deep-Dish" "Liquor Store"], + :phone "415-497-3039", + :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"}] + [{:service "foursquare", + :foursquare-photo-id "90c4812a-a426-4776-aeb1-81e4770c7887", + :mayor "sameer"} + "Mission Free-Range Liquor Store is a popular and fantastic place to watch the Giants game on Taco Tuesday." + {:small + "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/small.jpg", + :medium + "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/med.jpg", + :large + "http://cloudfront.net/57374d95-ff55-4a20-b98e-191dfc08ce75/large.jpg"} + {:name "Mission Free-Range Liquor Store", + :categories ["Free-Range" "Liquor Store"], + :phone "415-041-3816", + :id "6e665924-8e2c-42ab-af58-23a27f017e37"}] + [{:service "yelp", + :yelp-photo-id "d2643a6d-6669-48e5-890e-080aaff6d1e7", + :categories ["British" "Bakery"]} + "SoMa British Bakery is a wonderful and historical place to have a drink during winter." + {:small + "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/small.jpg", + :medium + "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/med.jpg", + :large + "http://cloudfront.net/7ee329cb-d9c3-48cf-8486-639e39df7329/large.jpg"} + {:name "SoMa British Bakery", + :categories ["British" "Bakery"], + :phone "415-909-5728", + :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"}] + [{:service "foursquare", + :foursquare-photo-id "7ae76e90-44db-483e-a5bc-2cb8ec65fa61", + :mayor "jessica"} + "Market St. Gluten-Free Café is a great and modern place to sip a glass of expensive wine Friday nights." + {:small + "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/small.jpg", + :medium + "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/med.jpg", + :large + "http://cloudfront.net/723c33fc-4901-4403-9f13-f451f98be98b/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "twitter", + :mentions ["@marina_no_msg_sushi"], + :tags ["#no-msg" "#sushi"], + :username "jane"} + "Marina No-MSG Sushi is a fantastic and classic place to have brunch with your pet dog." + {:small + "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/small.jpg", + :medium + "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/med.jpg", + :large + "http://cloudfront.net/c338a1ea-38e3-4409-9c0e-5099609318aa/large.jpg"} + {:name "Marina No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-856-5937", + :id "d51013a3-8547-4705-a5f0-cb11d8206481"}] + [{:service "foursquare", + :foursquare-photo-id "1b34eb04-290c-409e-b63c-ee82fff83878", + :mayor "mandy"} + "Sunset American Churros is a underappreciated and world-famous place to have brunch with friends." + {:small + "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/small.jpg", + :medium + "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/med.jpg", + :large + "http://cloudfront.net/56669abd-119c-4a02-abfc-d9acdd1e84fc/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "flare", :username "biggie"} + "Sameer's Pizza Liquor Store is a decent and underground place to meet new friends the second Saturday of the month." + {:small + "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/small.jpg", + :medium + "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/med.jpg", + :large + "http://cloudfront.net/010b1a6c-8ddf-4ea8-9c30-ab990356a5eb/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "yelp", + :yelp-photo-id "79616795-0585-478a-9998-8c9b0169005e", + :categories ["Deep-Dish" "Eatery"]} + "SF Deep-Dish Eatery is a delicious and modern place to watch the Warriors game on Saturday night." + {:small + "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/small.jpg", + :medium + "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/med.jpg", + :large + "http://cloudfront.net/29b0f569-4023-455e-bfbf-4e2e799c6f6e/large.jpg"} + {:name "SF Deep-Dish Eatery", + :categories ["Deep-Dish" "Eatery"], + :phone "415-476-9257", + :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"}] + [{:service "foursquare", + :foursquare-photo-id "c00e6384-fc26-4056-86fc-95df69092552", + :mayor "kyle"} + "SF Afgan Restaurant is a fantastic and exclusive place to nurse a hangover in June." + {:small + "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/small.jpg", + :medium + "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/med.jpg", + :large + "http://cloudfront.net/9af6de8f-b24f-4f36-8991-0888e7dbad4e/large.jpg"} + {:name "SF Afgan Restaurant", + :categories ["Afgan" "Restaurant"], + :phone "415-451-4697", + :id "66ccc68a-db9a-470c-a17b-7764d23daced"}] + [{:service "facebook", + :facebook-photo-id "d4c43b07-932f-42d3-b70f-29306c9f0746", + :url + "http://facebook.com/photos/d4c43b07-932f-42d3-b70f-29306c9f0746"} + "Pacific Heights Red White & Blue Bar & Grill is a swell and atmospheric place to conduct a business meeting the second Saturday of the month." + {:small + "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/small.jpg", + :medium + "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/med.jpg", + :large + "http://cloudfront.net/1c465d71-1c46-492f-883a-a069df6048ce/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "flare", :username "rasta_toucan"} + "Lower Pac Heights Cage-Free Coffee House is a acceptable and family-friendly place to nurse a hangover during winter." + {:small + "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/small.jpg", + :medium + "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/med.jpg", + :large + "http://cloudfront.net/7cb07300-7f55-4634-b998-4999aa1b4fb8/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "yelp", + :yelp-photo-id "38bc4d27-d28f-434b-b89d-5b88384a1c9b", + :categories ["American" "Grill"]} + "Oakland American Grill is a amazing and swell place to catch a bite to eat the first Sunday of the month." + {:small + "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/small.jpg", + :medium + "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/med.jpg", + :large + "http://cloudfront.net/dd1aaaba-124f-4722-8a46-33f2bcb826a4/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "twitter", + :mentions ["@joes_no_msg_sushi"], + :tags ["#no-msg" "#sushi"], + :username "joe"} + "Joe's No-MSG Sushi is a well-decorated and delicious place to catch a bite to eat on Thursdays." + {:small + "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/small.jpg", + :medium + "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/med.jpg", + :large + "http://cloudfront.net/27579436-073b-45b3-91c8-49f361fecefd/large.jpg"} + {:name "Joe's No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-739-8157", + :id "9ff21570-cd5b-415e-933a-52144f551b86"}] + [{:service "flare", :username "jessica"} + "Rasta's British Food Truck is a historical and well-decorated place to take visiting friends and relatives on a Tuesday afternoon." + {:small + "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/small.jpg", + :medium + "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/med.jpg", + :large + "http://cloudfront.net/6274a518-20f4-4db9-bcad-473c4f452841/large.jpg"} + {:name "Rasta's British Food Truck", + :categories ["British" "Food Truck"], + :phone "415-958-9031", + :id "b6616c97-01d0-488f-a855-bcd6efe2b899"}] + [{:service "flare", :username "tupac"} + "Polk St. Deep-Dish Hotel & Restaurant is a groovy and horrible place to take visiting friends and relatives with your pet toucan." + {:small + "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/small.jpg", + :medium + "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/med.jpg", + :large + "http://cloudfront.net/c28e51fc-ac77-43ab-90d3-d660143a78fe/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "flare", :username "mandy"} + "Market St. Gluten-Free Café is a delicious and delicious place to catch a bite to eat on a Tuesday afternoon." + {:small + "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/small.jpg", + :medium + "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/med.jpg", + :large + "http://cloudfront.net/cd5d0bf5-1341-4340-877d-b4f22ae989c9/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "flare", :username "joe"} + "Marina Modern Sushi is a underground and family-friendly place to sip Champagne Friday nights." + {:small + "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/small.jpg", + :medium + "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/med.jpg", + :large + "http://cloudfront.net/1c3be2ad-9aa2-4bea-8153-6130512cd9e8/large.jpg"} + {:name "Marina Modern Sushi", + :categories ["Modern" "Sushi"], + :phone "415-393-7672", + :id "21807c63-ca4c-4468-9844-d0c2620fbdfc"}] + [{:service "yelp", + :yelp-photo-id "8e817a6f-5d3d-4fff-9c80-b5ed3e225095", + :categories ["Pizza" "Bakery"]} + "Pacific Heights Pizza Bakery is a underappreciated and popular place to sip a glass of expensive wine the first Sunday of the month." + {:small + "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/small.jpg", + :medium + "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/med.jpg", + :large + "http://cloudfront.net/ac8b7804-3e6b-4c2d-b78e-aa20530c9a35/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "flare", :username "mandy"} + "Kyle's Japanese Hotel & Restaurant is a amazing and family-friendly place to have breakfast in June." + {:small + "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/small.jpg", + :medium + "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/med.jpg", + :large + "http://cloudfront.net/9967c7f3-3da3-4891-b00e-8296898bbeb6/large.jpg"} + {:name "Kyle's Japanese Hotel & Restaurant", + :categories ["Japanese" "Hotel & Restaurant"], + :phone "415-337-5387", + :id "eced4f41-b627-4553-a297-888871038b69"}] + [{:service "facebook", + :facebook-photo-id "265681c6-d000-4618-87fc-f8e35eef9ee3", + :url + "http://facebook.com/photos/265681c6-d000-4618-87fc-f8e35eef9ee3"} + "Pacific Heights No-MSG Sushi is a groovy and groovy place to drink a craft beer when hungover." + {:small + "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/small.jpg", + :medium + "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/med.jpg", + :large + "http://cloudfront.net/6d5d0c28-8b65-4392-9fe9-3b85427a1cdb/large.jpg"} + {:name "Pacific Heights No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-354-9547", + :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"}] + [{:service "yelp", + :yelp-photo-id "69b17ca8-3a3e-4df2-a46b-a18d4d82283d", + :categories ["Homestyle" "Eatery"]} + "Joe's Homestyle Eatery is a decent and fantastic place to conduct a business meeting on public holidays." + {:small + "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/small.jpg", + :medium + "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/med.jpg", + :large + "http://cloudfront.net/0ad76a39-e0df-4212-a365-b5afb27bf7b6/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "flare", :username "jane"} + "Pacific Heights Irish Grill is a world-famous and delicious place to conduct a business meeting with friends." + {:small + "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/small.jpg", + :medium + "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/med.jpg", + :large + "http://cloudfront.net/4e773c36-2173-4583-967c-9d31f56b086d/large.jpg"} + {:name "Pacific Heights Irish Grill", + :categories ["Irish" "Grill"], + :phone "415-491-2202", + :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"}] + [{:service "twitter", + :mentions ["@marina_japanese_liquor_store"], + :tags ["#japanese" "#liquor" "#store"], + :username "bob"} + "Marina Japanese Liquor Store is a classic and groovy place to watch the Giants game Friday nights." + {:small + "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/small.jpg", + :medium + "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/med.jpg", + :large + "http://cloudfront.net/a3abdf98-ee64-4a7d-96a4-0983e7c6a616/large.jpg"} + {:name "Marina Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-587-9819", + :id "08fd0138-35dd-41b0-836d-1c652e95ffcd"}] + [{:service "flare", :username "amy"} + "Pacific Heights Soul Food Coffee House is a wonderful and underappreciated place to watch the Warriors game in July." + {:small + "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/small.jpg", + :medium + "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/med.jpg", + :large + "http://cloudfront.net/08e81253-a561-45d2-8715-e2e8a6d990a0/large.jpg"} + {:name "Pacific Heights Soul Food Coffee House", + :categories ["Soul Food" "Coffee House"], + :phone "415-838-3464", + :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"}] + [{:service "twitter", + :mentions ["@polk_st._korean_taqueria"], + :tags ["#korean" "#taqueria"], + :username "bob"} + "Polk St. Korean Taqueria is a amazing and historical place to people-watch on Saturday night." + {:small + "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/small.jpg", + :medium + "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/med.jpg", + :large + "http://cloudfront.net/a9ad1e78-ebe9-4248-b265-048c12945997/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "facebook", + :facebook-photo-id "8ce61a42-cb9c-4304-bada-3a8fb245bb64", + :url + "http://facebook.com/photos/8ce61a42-cb9c-4304-bada-3a8fb245bb64"} + "Lucky's Cage-Free Liquor Store is a delicious and amazing place to catch a bite to eat on a Tuesday afternoon." + {:small + "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/small.jpg", + :medium + "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/med.jpg", + :large + "http://cloudfront.net/9e74ec8b-ecd9-4987-b2d8-63a84938d699/large.jpg"} + {:name "Lucky's Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-341-3219", + :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"}] + [{:service "facebook", + :facebook-photo-id "4e3d4ad7-54ba-4f55-baf7-4ad223bcfaf6", + :url + "http://facebook.com/photos/4e3d4ad7-54ba-4f55-baf7-4ad223bcfaf6"} + "SoMa Japanese Churros is a wonderful and modern place to conduct a business meeting during winter." + {:small + "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/small.jpg", + :medium + "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/med.jpg", + :large + "http://cloudfront.net/41114760-4e0c-4555-a67c-9018ec34ef73/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "facebook", + :facebook-photo-id "91e06ea0-df70-48e2-bb43-6acfe787d203", + :url + "http://facebook.com/photos/91e06ea0-df70-48e2-bb43-6acfe787d203"} + "SoMa Old-Fashioned Pizzeria is a well-decorated and horrible place to meet new friends in the fall." + {:small + "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/small.jpg", + :medium + "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/med.jpg", + :large + "http://cloudfront.net/dd0eb180-3e40-4ac6-becb-653a32f2d43d/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "flare", :username "cam_saul"} + "Oakland European Liquor Store is a popular and historical place to sip Champagne with your pet toucan." + {:small + "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/small.jpg", + :medium + "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/med.jpg", + :large + "http://cloudfront.net/9c28ae3e-e583-4ec2-9646-a58af7de6e5a/large.jpg"} + {:name "Oakland European Liquor Store", + :categories ["European" "Liquor Store"], + :phone "415-559-1516", + :id "e342e7b7-e82d-475d-a822-b2df9c84850d"}] + [{:service "twitter", + :mentions ["@pacific_heights_pizza_bakery"], + :tags ["#pizza" "#bakery"], + :username "mandy"} + "Pacific Heights Pizza Bakery is a classic and classic place to have a birthday party on public holidays." + {:small + "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/small.jpg", + :medium + "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/med.jpg", + :large + "http://cloudfront.net/114749c9-8bf7-4a65-9757-ac8e42be72bb/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "flare", :username "tupac"} + "Sameer's GMO-Free Restaurant is a decent and family-friendly place to take visiting friends and relatives during winter." + {:small + "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/small.jpg", + :medium + "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/med.jpg", + :large + "http://cloudfront.net/ce7cc9e2-9948-4ca3-9803-87d565168cbb/large.jpg"} + {:name "Sameer's GMO-Free Restaurant", + :categories ["GMO-Free" "Restaurant"], + :phone "415-128-9430", + :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"}] + [{:service "facebook", + :facebook-photo-id "f420e2a8-13ce-4753-b30e-493d6e59f7ce", + :url + "http://facebook.com/photos/f420e2a8-13ce-4753-b30e-493d6e59f7ce"} + "Pacific Heights Pizza Bakery is a great and swell place to take a date on Thursdays." + {:small + "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/small.jpg", + :medium + "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/med.jpg", + :large + "http://cloudfront.net/76d68d0c-7118-41eb-9c5b-b45c1096b791/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "yelp", + :yelp-photo-id "22fac240-8066-4a4d-a3a4-08c268bca17d", + :categories ["Gluten-Free" "Gastro Pub"]} + "Lucky's Gluten-Free Gastro Pub is a exclusive and overrated place to nurse a hangover in June." + {:small + "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/small.jpg", + :medium + "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/med.jpg", + :large + "http://cloudfront.net/8651fa50-125f-47bf-99ec-2e950facd1fd/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "flare", :username "jane"} + "Oakland Low-Carb Bakery is a classic and modern place to have brunch weekday afternoons." + {:small + "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/small.jpg", + :medium + "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/med.jpg", + :large + "http://cloudfront.net/ecb14fc2-cbc6-4f8f-b879-c6884bec5fb0/large.jpg"} + {:name "Oakland Low-Carb Bakery", + :categories ["Low-Carb" "Bakery"], + :phone "415-546-0101", + :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"}] + [{:service "flare", :username "joe"} + "Tenderloin Gormet Restaurant is a underground and classic place to have breakfast weekend mornings." + {:small + "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/small.jpg", + :medium + "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/med.jpg", + :large + "http://cloudfront.net/d40b4507-d30a-4c0c-a1da-bf78e510607d/large.jpg"} + {:name "Tenderloin Gormet Restaurant", + :categories ["Gormet" "Restaurant"], + :phone "415-127-4197", + :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"}] + [{:service "flare", :username "lucky_pigeon"} + "SoMa TaquerÃa Diner is a historical and world-famous place to watch the Warriors game on Saturday night." + {:small + "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/small.jpg", + :medium + "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/med.jpg", + :large + "http://cloudfront.net/1e4cb1bb-ec7a-45b4-bcd8-230a32350a51/large.jpg"} + {:name "SoMa TaquerÃa Diner", + :categories ["TaquerÃa" "Diner"], + :phone "415-947-9521", + :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"}] + [{:service "facebook", + :facebook-photo-id "9edda8de-49c1-41f7-8c22-06c0fc7cfc10", + :url + "http://facebook.com/photos/9edda8de-49c1-41f7-8c22-06c0fc7cfc10"} + "Kyle's Low-Carb Grill is a world-famous and amazing place to nurse a hangover on Thursdays." + {:small + "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/small.jpg", + :medium + "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/med.jpg", + :large + "http://cloudfront.net/7cf33495-cc12-499d-b243-53100bd76df6/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "facebook", + :facebook-photo-id "234f4d52-f5b6-4214-9fbc-13ae88e25cdb", + :url + "http://facebook.com/photos/234f4d52-f5b6-4214-9fbc-13ae88e25cdb"} + "Haight Soul Food Pop-Up Food Stand is a modern and acceptable place to watch the Warriors game in the spring." + {:small + "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/small.jpg", + :medium + "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/med.jpg", + :large + "http://cloudfront.net/be0f6011-ee1b-47ae-9d71-d7e8561e25a1/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "foursquare", + :foursquare-photo-id "ced4f00d-21ed-4b10-90f6-8d5db5cc24e7", + :mayor "bob"} + "Rasta's Paleo Churros is a fantastic and classic place to nurse a hangover during summer." + {:small + "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/small.jpg", + :medium + "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/med.jpg", + :large + "http://cloudfront.net/51726cf2-f9af-476c-9eea-e8ef16a03cc4/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "flare", :username "mandy"} + "Rasta's Paleo Churros is a fantastic and delicious place to take a date when hungover." + {:small + "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/small.jpg", + :medium + "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/med.jpg", + :large + "http://cloudfront.net/3ebb84e2-a51e-4e2d-913b-3e20974f0ed5/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "flare", :username "biggie"} + "SF Afgan Restaurant is a groovy and family-friendly place to have a after-work cocktail weekend mornings." + {:small + "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/small.jpg", + :medium + "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/med.jpg", + :large + "http://cloudfront.net/6ad9e353-e342-4aa3-beca-dff8a2c80506/large.jpg"} + {:name "SF Afgan Restaurant", + :categories ["Afgan" "Restaurant"], + :phone "415-451-4697", + :id "66ccc68a-db9a-470c-a17b-7764d23daced"}] + [{:service "twitter", + :mentions ["@mission_homestyle_churros"], + :tags ["#homestyle" "#churros"], + :username "biggie"} + "Mission Homestyle Churros is a historical and well-decorated place to catch a bite to eat weekend evenings." + {:small + "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/small.jpg", + :medium + "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/med.jpg", + :large + "http://cloudfront.net/f00dd865-2e18-4924-aa16-fb50cc0829de/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "yelp", + :yelp-photo-id "8ebf793a-f6e8-4113-8d3b-769d6eb1a0bf", + :categories ["Free-Range" "Eatery"]} + "Pacific Heights Free-Range Eatery is a groovy and overrated place to sip a glass of expensive wine in the spring." + {:small + "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/small.jpg", + :medium + "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/med.jpg", + :large + "http://cloudfront.net/7ca05f25-488c-47ec-b308-9ac9aa506f3a/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "foursquare", + :foursquare-photo-id "1a1168ce-aa45-485f-a427-309eeb536510", + :mayor "cam_saul"} + "Marina Cage-Free Liquor Store is a delicious and fantastic place to nurse a hangover during summer." + {:small + "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/small.jpg", + :medium + "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/med.jpg", + :large + "http://cloudfront.net/39c91431-8787-4240-b706-911793d6826c/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "yelp", + :yelp-photo-id "989113fd-f95f-4e62-ac21-a428e2b72334", + :categories ["Red White & Blue" "Pizzeria"]} + "Tenderloin Red White & Blue Pizzeria is a atmospheric and atmospheric place to nurse a hangover during winter." + {:small + "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/small.jpg", + :medium + "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/med.jpg", + :large + "http://cloudfront.net/fcf38a30-edfb-4dd0-b2e1-91ddfae20425/large.jpg"} + {:name "Tenderloin Red White & Blue Pizzeria", + :categories ["Red White & Blue" "Pizzeria"], + :phone "415-719-8143", + :id "eba3dbcd-100a-4f38-a701-e0dec157f437"}] + [{:service "facebook", + :facebook-photo-id "4384296d-a41c-44c8-bff9-a5d64bb01f44", + :url + "http://facebook.com/photos/4384296d-a41c-44c8-bff9-a5d64bb01f44"} + "Polk St. Mexican Coffee House is a classic and swell place to watch the Giants game weekend mornings." + {:small + "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/small.jpg", + :medium + "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/med.jpg", + :large + "http://cloudfront.net/ffe98db7-7824-4685-99ec-eaebf63d9248/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "yelp", + :yelp-photo-id "4623b716-fb61-4349-8f79-cef3706d6b0d", + :categories ["Chinese" "Liquor Store"]} + "Mission Chinese Liquor Store is a horrible and atmospheric place to have a birthday party when hungover." + {:small + "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/small.jpg", + :medium + "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/med.jpg", + :large + "http://cloudfront.net/9c3f49c7-3924-4213-b09d-aaa9db7993f3/large.jpg"} + {:name "Mission Chinese Liquor Store", + :categories ["Chinese" "Liquor Store"], + :phone "415-906-6919", + :id "00132b5b-31fc-46f0-a288-f547f23477ee"}] + [{:service "yelp", + :yelp-photo-id "a4e6c1c8-afaf-48ed-b18d-755f9c6dd4b6", + :categories ["Gormet" "Restaurant"]} + "Tenderloin Gormet Restaurant is a overrated and swell place to nurse a hangover in June." + {:small + "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/small.jpg", + :medium + "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/med.jpg", + :large + "http://cloudfront.net/817b3f0f-3cee-48ac-bfec-9b5ad18d8350/large.jpg"} + {:name "Tenderloin Gormet Restaurant", + :categories ["Gormet" "Restaurant"], + :phone "415-127-4197", + :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"}] + [{:service "flare", :username "biggie"} + "Tenderloin Paleo Hotel & Restaurant is a modern and groovy place to pitch an investor the second Saturday of the month." + {:small + "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/small.jpg", + :medium + "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/med.jpg", + :large + "http://cloudfront.net/8357d4c5-a502-4b71-ab70-25a5090ac16c/large.jpg"} + {:name "Tenderloin Paleo Hotel & Restaurant", + :categories ["Paleo" "Hotel & Restaurant"], + :phone "415-402-1652", + :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"}] + [{:service "yelp", + :yelp-photo-id "cc9465c4-b3af-45f1-aa0d-8b38c5a6a366", + :categories ["Free-Range" "Eatery"]} + "Pacific Heights Free-Range Eatery is a underground and family-friendly place to have a after-work cocktail with friends." + {:small + "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/small.jpg", + :medium + "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/med.jpg", + :large + "http://cloudfront.net/7a4260f9-efef-4e28-8c78-4b2d2f36d30d/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "yelp", + :yelp-photo-id "6b5975f9-77f6-4375-be86-5fd047a5493a", + :categories ["Deep-Dish" "Ice Cream Truck"]} + "Lower Pac Heights Deep-Dish Ice Cream Truck is a classic and atmospheric place to meet new friends weekend mornings." + {:small + "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/small.jpg", + :medium + "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/med.jpg", + :large + "http://cloudfront.net/e7bf1145-72d9-4229-b464-d33209e3549a/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", + :categories ["Deep-Dish" "Ice Cream Truck"], + :phone "415-495-1414", + :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"}] + [{:service "yelp", + :yelp-photo-id "dd02f753-593e-4bc5-b370-08a6fe46de96", + :categories ["BBQ" "Churros"]} + "Mission BBQ Churros is a family-friendly and delicious place to people-watch on Saturday night." + {:small + "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/small.jpg", + :medium + "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/med.jpg", + :large + "http://cloudfront.net/24d757ff-5f11-4864-8395-744c78e36ade/large.jpg"} + {:name "Mission BBQ Churros", + :categories ["BBQ" "Churros"], + :phone "415-406-5374", + :id "429ea81a-02c5-449f-bfa7-03a11b227f1f"}] + [{:service "twitter", + :mentions ["@pacific_heights_soul_food_coffee_house"], + :tags ["#soul" "#food" "#coffee" "#house"], + :username "mandy"} + "Pacific Heights Soul Food Coffee House is a family-friendly and wonderful place to pitch an investor weekday afternoons." + {:small + "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/small.jpg", + :medium + "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/med.jpg", + :large + "http://cloudfront.net/24928248-5bf6-4a49-a2aa-2331d9d83990/large.jpg"} + {:name "Pacific Heights Soul Food Coffee House", + :categories ["Soul Food" "Coffee House"], + :phone "415-838-3464", + :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"}] + [{:service "yelp", + :yelp-photo-id "bf759d74-af3f-4a57-addb-1349f2b2f8c9", + :categories ["Soul Food" "Pizzeria"]} + "Mission Soul Food Pizzeria is a groovy and underground place to take visiting friends and relatives in July." + {:small + "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/small.jpg", + :medium + "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/med.jpg", + :large + "http://cloudfront.net/c60e9321-eae7-43a9-aa60-654d6be34177/large.jpg"} + {:name "Mission Soul Food Pizzeria", + :categories ["Soul Food" "Pizzeria"], + :phone "415-437-3479", + :id "9905fe61-44cb-4626-843b-5d725c7949bb"}] + [{:service "flare", :username "rasta_toucan"} + "Chinatown American Bakery is a decent and great place to have brunch during winter." + {:small + "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/small.jpg", + :medium + "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/med.jpg", + :large + "http://cloudfront.net/301b02c7-4539-4713-9d5f-3f478ca2b2c3/large.jpg"} + {:name "Chinatown American Bakery", + :categories ["American" "Bakery"], + :phone "415-658-7393", + :id "cf55cdbd-c614-4be1-8496-0e11b195d16f"}] + [{:service "twitter", + :mentions ["@cams_old_fashioned_coffee_house"], + :tags ["#old-fashioned" "#coffee" "#house"], + :username "lucky_pigeon"} + "Cam's Old-Fashioned Coffee House is a groovy and popular place to have a birthday party in the fall." + {:small + "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/small.jpg", + :medium + "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/med.jpg", + :large + "http://cloudfront.net/1aed40ef-77a4-4207-9bc5-c657fad18f61/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-868-2973", + :id "27592c2b-e682-44bb-be28-8e9a622becca"}] + [{:service "facebook", + :facebook-photo-id "251a7bf8-73d3-44b8-9f55-2ee0048158d1", + :url + "http://facebook.com/photos/251a7bf8-73d3-44b8-9f55-2ee0048158d1"} + "Pacific Heights No-MSG Sushi is a well-decorated and amazing place to conduct a business meeting in the fall." + {:small + "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/small.jpg", + :medium + "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/med.jpg", + :large + "http://cloudfront.net/4e1edf59-97fa-456b-94ff-bc82c7282d86/large.jpg"} + {:name "Pacific Heights No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-354-9547", + :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"}] + [{:service "facebook", + :facebook-photo-id "b2fa77dd-6e6d-440e-8f12-caa3ea7c120c", + :url + "http://facebook.com/photos/b2fa77dd-6e6d-440e-8f12-caa3ea7c120c"} + "Mission Chinese Liquor Store is a underappreciated and acceptable place to have breakfast with friends." + {:small + "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/small.jpg", + :medium + "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/med.jpg", + :large + "http://cloudfront.net/4276a5e5-3821-4fba-bf48-32c8d9fb2ba1/large.jpg"} + {:name "Mission Chinese Liquor Store", + :categories ["Chinese" "Liquor Store"], + :phone "415-906-6919", + :id "00132b5b-31fc-46f0-a288-f547f23477ee"}] + [{:service "flare", :username "sameer"} + "Kyle's Old-Fashioned Pop-Up Food Stand is a underappreciated and groovy place to catch a bite to eat on a Tuesday afternoon." + {:small + "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/small.jpg", + :medium + "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/med.jpg", + :large + "http://cloudfront.net/36d9d88d-a911-4e0e-911a-16645a2d939e/large.jpg"} + {:name "Kyle's Old-Fashioned Pop-Up Food Stand", + :categories ["Old-Fashioned" "Pop-Up Food Stand"], + :phone "415-638-8972", + :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"}] + [{:service "twitter", + :mentions ["@sameers_gmo_free_restaurant"], + :tags ["#gmo-free" "#restaurant"], + :username "tupac"} + "Sameer's GMO-Free Restaurant is a groovy and modern place to people-watch after baseball games." + {:small + "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/small.jpg", + :medium + "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/med.jpg", + :large + "http://cloudfront.net/74209bf9-650b-4130-8095-95ce2caf22bf/large.jpg"} + {:name "Sameer's GMO-Free Restaurant", + :categories ["GMO-Free" "Restaurant"], + :phone "415-128-9430", + :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"}] + [{:service "twitter", + :mentions ["@haight_soul_food_pop_up_food_stand"], + :tags ["#soul" "#food" "#pop-up" "#food" "#stand"], + :username "kyle"} + "Haight Soul Food Pop-Up Food Stand is a underground and modern place to have breakfast on a Tuesday afternoon." + {:small + "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/small.jpg", + :medium + "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/med.jpg", + :large + "http://cloudfront.net/8f613909-550f-4d79-96f6-dc498ff65d1b/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "flare", :username "mandy"} + "Pacific Heights Irish Grill is a acceptable and world-famous place to have a after-work cocktail on Saturday night." + {:small + "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/small.jpg", + :medium + "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/med.jpg", + :large + "http://cloudfront.net/a7c43f41-345f-43ce-8bb9-48b4717d1cfd/large.jpg"} + {:name "Pacific Heights Irish Grill", + :categories ["Irish" "Grill"], + :phone "415-491-2202", + :id "d6b92dfc-56e9-4f65-b1d0-595f120043d9"}] + [{:service "flare", :username "sameer"} + "Mission Homestyle Churros is a decent and horrible place to pitch an investor on Taco Tuesday." + {:small + "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/small.jpg", + :medium + "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/med.jpg", + :large + "http://cloudfront.net/da92cb40-5920-49cf-b065-4cb558f201df/large.jpg"} + {:name "Mission Homestyle Churros", + :categories ["Homestyle" "Churros"], + :phone "415-343-4489", + :id "21d903d3-8bdb-4b7d-b288-6063ad48af44"}] + [{:service "foursquare", + :foursquare-photo-id "95609cc6-1197-4713-bc72-7e3e799dbb6c", + :mayor "jessica"} + "Haight Soul Food Sushi is a well-decorated and wonderful place to drink a craft beer with friends." + {:small + "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/small.jpg", + :medium + "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/med.jpg", + :large + "http://cloudfront.net/918a829c-61a2-439f-8607-b5c42f39345c/large.jpg"} + {:name "Haight Soul Food Sushi", + :categories ["Soul Food" "Sushi"], + :phone "415-371-8026", + :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"}] + [{:service "foursquare", + :foursquare-photo-id "002d03c2-9c91-4160-a9e8-4603ae977245", + :mayor "sameer"} + "Market St. Homestyle Pop-Up Food Stand is a historical and great place to nurse a hangover weekday afternoons." + {:small + "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/small.jpg", + :medium + "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/med.jpg", + :large + "http://cloudfront.net/8f9473e3-1128-4383-8f9d-3a2009b97f56/large.jpg"} + {:name "Market St. Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-213-3030", + :id "2d873280-e43d-449e-9940-af96ae7df718"}] + [{:service "yelp", + :yelp-photo-id "6ccfda4c-84d2-4108-8cb1-699d28cd1574", + :categories ["European" "Sushi"]} + "Sameer's European Sushi is a atmospheric and horrible place to take a date on Taco Tuesday." + {:small + "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/small.jpg", + :medium + "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/med.jpg", + :large + "http://cloudfront.net/e0767b0f-2938-4e84-be6f-9c7e6ba8ff82/large.jpg"} + {:name "Sameer's European Sushi", + :categories ["European" "Sushi"], + :phone "415-035-2474", + :id "7de6b3ef-7b53-4831-bf76-43123874f8ce"}] + [{:service "flare", :username "bob"} + "Polk St. Korean Taqueria is a family-friendly and horrible place to take a date in the spring." + {:small + "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/small.jpg", + :medium + "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/med.jpg", + :large + "http://cloudfront.net/53a88ef8-505f-4923-9a13-eb686c6e7f25/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "facebook", + :facebook-photo-id "276865a5-e7d3-40f6-a2b2-62172caf3024", + :url + "http://facebook.com/photos/276865a5-e7d3-40f6-a2b2-62172caf3024"} + "Polk St. Red White & Blue Café is a popular and swell place to have a birthday party in June." + {:small + "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/small.jpg", + :medium + "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/med.jpg", + :large + "http://cloudfront.net/eed1df2e-ed28-4a7c-a9b4-9b6511837414/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "flare", :username "kyle"} + "Lucky's Gluten-Free Gastro Pub is a underappreciated and historical place to watch the Warriors game with friends." + {:small + "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/small.jpg", + :medium + "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/med.jpg", + :large + "http://cloudfront.net/4020585b-9a66-4cb1-b5f2-2e7e3a1d26c2/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "facebook", + :facebook-photo-id "26cff009-fce9-4439-a263-39df23a3b143", + :url + "http://facebook.com/photos/26cff009-fce9-4439-a263-39df23a3b143"} + "Rasta's Paleo Café is a decent and swell place to have a drink weekend evenings." + {:small + "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/small.jpg", + :medium + "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/med.jpg", + :large + "http://cloudfront.net/7bdece4f-bc76-46a3-bbf6-c8a9c2012e79/large.jpg"} + {:name "Rasta's Paleo Café", + :categories ["Paleo" "Café"], + :phone "415-392-6341", + :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"}] + [{:service "yelp", + :yelp-photo-id "b82466db-dddb-451c-ba09-47d520df8730", + :categories ["Mexican" "Coffee House"]} + "Polk St. Mexican Coffee House is a modern and historical place to have brunch when hungover." + {:small + "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/small.jpg", + :medium + "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/med.jpg", + :large + "http://cloudfront.net/34084c76-3ff7-4bf0-a0f9-bcf9a3767f03/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "foursquare", + :foursquare-photo-id "686a4c8a-51ca-4f6b-b216-d4b53dc11944", + :mayor "rasta_toucan"} + "Polk St. Korean Taqueria is a amazing and classic place to take visiting friends and relatives weekend evenings." + {:small + "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/small.jpg", + :medium + "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/med.jpg", + :large + "http://cloudfront.net/95f2673c-fa99-4429-918d-45095a699691/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "flare", :username "sameer"} + "Pacific Heights Free-Range Eatery is a groovy and swell place to sip a glass of expensive wine during summer." + {:small + "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/small.jpg", + :medium + "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/med.jpg", + :large + "http://cloudfront.net/10f63b40-459d-446a-9bc9-a7a6a078d77a/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "facebook", + :facebook-photo-id "b136f58c-6b1f-433b-bce8-c803040ff9bd", + :url + "http://facebook.com/photos/b136f58c-6b1f-433b-bce8-c803040ff9bd"} + "Market St. Low-Carb Taqueria is a delicious and groovy place to watch the Warriors game with friends." + {:small + "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/small.jpg", + :medium + "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/med.jpg", + :large + "http://cloudfront.net/1d458234-44b2-47f7-9038-1110a9a0dc0d/large.jpg"} + {:name "Market St. Low-Carb Taqueria", + :categories ["Low-Carb" "Taqueria"], + :phone "415-751-6525", + :id "f30eb85b-f048-4d8c-8008-3c2876125061"}] + [{:service "facebook", + :facebook-photo-id "024c7142-3009-4fcc-9094-f7975b939e80", + :url + "http://facebook.com/photos/024c7142-3009-4fcc-9094-f7975b939e80"} + "Pacific Heights Pizza Bakery is a classic and historical place to meet new friends during winter." + {:small + "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/small.jpg", + :medium + "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/med.jpg", + :large + "http://cloudfront.net/903495d4-56b9-4ac6-a141-fd30ad37f00c/large.jpg"} + {:name "Pacific Heights Pizza Bakery", + :categories ["Pizza" "Bakery"], + :phone "415-006-0149", + :id "7fda37a5-810f-4902-b571-54afe583f0dd"}] + [{:service "flare", :username "lucky_pigeon"} + "Polk St. Mexican Coffee House is a horrible and underground place to have brunch with friends." + {:small + "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/small.jpg", + :medium + "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/med.jpg", + :large + "http://cloudfront.net/4c06d251-8f13-4195-b5d6-68857091b26f/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "facebook", + :facebook-photo-id "10073113-dee8-47d0-a994-eb84a3099ad4", + :url + "http://facebook.com/photos/10073113-dee8-47d0-a994-eb84a3099ad4"} + "Marina Low-Carb Food Truck is a swell and world-famous place to watch the Warriors game with friends." + {:small + "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/small.jpg", + :medium + "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/med.jpg", + :large + "http://cloudfront.net/95b4c506-0290-4042-ba97-de6d46d23cbe/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "yelp", + :yelp-photo-id "59df02f3-5581-43bb-8875-ce689355302d", + :categories ["Soul Food" "Pop-Up Food Stand"]} + "Haight Soul Food Pop-Up Food Stand is a exclusive and amazing place to nurse a hangover during summer." + {:small + "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/small.jpg", + :medium + "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/med.jpg", + :large + "http://cloudfront.net/fd764dce-022d-45ca-80ac-6603f807de11/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "facebook", + :facebook-photo-id "061a7423-a561-4ed6-9b75-20053659738c", + :url + "http://facebook.com/photos/061a7423-a561-4ed6-9b75-20053659738c"} + "Polk St. Red White & Blue Café is a family-friendly and wonderful place to have brunch weekday afternoons." + {:small + "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/small.jpg", + :medium + "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/med.jpg", + :large + "http://cloudfront.net/0e61715d-c180-4fd0-942a-1917392c5b18/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "yelp", + :yelp-photo-id "3326a855-2959-486f-a0f3-21c21211361f", + :categories ["Paleo" "Churros"]} + "Rasta's Paleo Churros is a classic and overrated place to catch a bite to eat in June." + {:small + "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/small.jpg", + :medium + "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/med.jpg", + :large + "http://cloudfront.net/6298f645-2abd-4b53-821a-6d3d1dd5eab7/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "flare", :username "rasta_toucan"} + "Nob Hill Korean Taqueria is a great and popular place to take a date when hungover." + {:small + "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/small.jpg", + :medium + "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/med.jpg", + :large + "http://cloudfront.net/0717cf07-cc25-4938-82ad-582fbb27753c/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "yelp", + :yelp-photo-id "cc977fda-1fb0-4e78-8699-1558c5f42e69", + :categories ["Japanese" "Ice Cream Truck"]} + "Tenderloin Japanese Ice Cream Truck is a swell and acceptable place to nurse a hangover on Thursdays." + {:small + "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/small.jpg", + :medium + "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/med.jpg", + :large + "http://cloudfront.net/aa6e3b77-7eba-4817-8510-e1e19e96ae2a/large.jpg"} + {:name "Tenderloin Japanese Ice Cream Truck", + :categories ["Japanese" "Ice Cream Truck"], + :phone "415-856-0371", + :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"}] + [{:service "yelp", + :yelp-photo-id "40052a53-f10e-4d38-806f-d88d2e77faf2", + :categories ["Soul Food" "Sushi"]} + "Haight Soul Food Sushi is a decent and swell place to conduct a business meeting in the spring." + {:small + "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/small.jpg", + :medium + "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/med.jpg", + :large + "http://cloudfront.net/677835f0-ba4f-40b1-bd8b-993760fa3117/large.jpg"} + {:name "Haight Soul Food Sushi", + :categories ["Soul Food" "Sushi"], + :phone "415-371-8026", + :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"}] + [{:service "flare", :username "mandy"} + "Kyle's Low-Carb Grill is a classic and wonderful place to have a drink when hungover." + {:small + "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/small.jpg", + :medium + "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/med.jpg", + :large + "http://cloudfront.net/7f021f2d-479e-4115-a0ad-18115f906184/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "foursquare", + :foursquare-photo-id "ed2871fe-d033-4d81-87b1-86550a879d9c", + :mayor "rasta_toucan"} + "Rasta's Paleo Churros is a modern and horrible place to watch the Giants game on Taco Tuesday." + {:small + "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/small.jpg", + :medium + "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/med.jpg", + :large + "http://cloudfront.net/cb993d22-054e-4848-ac71-ad7071d1df7f/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "yelp", + :yelp-photo-id "964cf5f3-9d68-4fa5-8fff-93d09fe23f13", + :categories ["Chinese" "Restaurant"]} + "Kyle's Chinese Restaurant is a delicious and well-decorated place to pitch an investor the first Sunday of the month." + {:small + "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/small.jpg", + :medium + "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/med.jpg", + :large + "http://cloudfront.net/750fb652-b5a7-4107-8c46-8e28bc3e86f6/large.jpg"} + {:name "Kyle's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-298-9499", + :id "de08b3c7-9929-40d8-8c20-dd9317613c17"}] + [{:service "facebook", + :facebook-photo-id "0fc82427-48c6-468d-8813-9dca3289e519", + :url + "http://facebook.com/photos/0fc82427-48c6-468d-8813-9dca3289e519"} + "Rasta's Paleo Café is a exclusive and popular place to sip Champagne on Saturday night." + {:small + "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/small.jpg", + :medium + "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/med.jpg", + :large + "http://cloudfront.net/5faf0fda-041a-446d-aba4-54b7a6ceaa18/large.jpg"} + {:name "Rasta's Paleo Café", + :categories ["Paleo" "Café"], + :phone "415-392-6341", + :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"}] + [{:service "foursquare", + :foursquare-photo-id "6c35de08-10de-4ca4-acb8-a659d005f73c", + :mayor "bob"} + "Kyle's Free-Range Taqueria is a horrible and exclusive place to sip a glass of expensive wine with your pet toucan." + {:small + "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/small.jpg", + :medium + "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/med.jpg", + :large + "http://cloudfront.net/865964d4-35c0-40e2-9029-584fc6f393ce/large.jpg"} + {:name "Kyle's Free-Range Taqueria", + :categories ["Free-Range" "Taqueria"], + :phone "415-201-7832", + :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"}] + [{:service "foursquare", + :foursquare-photo-id "6b2657cc-f404-4507-99d0-89df941c8c19", + :mayor "jessica"} + "Sunset Deep-Dish Hotel & Restaurant is a modern and acceptable place to take visiting friends and relatives the second Saturday of the month." + {:small + "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/small.jpg", + :medium + "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/med.jpg", + :large + "http://cloudfront.net/896eef7b-8a70-4706-a70f-f8fbf75acffc/large.jpg"} + {:name "Sunset Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-332-0978", + :id "a80745c7-af74-4579-8932-70dd488269e6"}] + [{:service "yelp", + :yelp-photo-id "d2f7fc44-73f5-4c46-b6ac-b4407bb0df0e", + :categories ["European" "Taqueria"]} + "Rasta's European Taqueria is a fantastic and great place to watch the Warriors game weekend mornings." + {:small + "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/small.jpg", + :medium + "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/med.jpg", + :large + "http://cloudfront.net/11b7f47b-1a68-43a9-b3be-0228fa19c70c/large.jpg"} + {:name "Rasta's European Taqueria", + :categories ["European" "Taqueria"], + :phone "415-631-1599", + :id "cb472880-ee6e-46e3-bd58-22cf33109aba"}] + [{:service "yelp", + :yelp-photo-id "e5070b25-6933-44e9-a96b-bc7273c13616", + :categories ["Homestyle" "Eatery"]} + "Joe's Homestyle Eatery is a amazing and delicious place to have a drink when hungover." + {:small + "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/small.jpg", + :medium + "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/med.jpg", + :large + "http://cloudfront.net/90957931-5a61-49dd-ab52-bdcf648e370f/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "flare", :username "tupac"} + "SoMa Japanese Churros is a acceptable and horrible place to have a birthday party with your pet dog." + {:small + "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/small.jpg", + :medium + "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/med.jpg", + :large + "http://cloudfront.net/91e5dd78-7a6d-4384-8252-943b8a860a2d/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "flare", :username "amy"} + "Haight Soul Food Pop-Up Food Stand is a groovy and swell place to have breakfast when hungover." + {:small + "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/small.jpg", + :medium + "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/med.jpg", + :large + "http://cloudfront.net/4337bc10-99dc-4f86-a9c2-a64e9d1804da/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "foursquare", + :foursquare-photo-id "532267f2-2b66-4adc-8c51-b8ee4509e548", + :mayor "kyle"} + "Mission Free-Range Liquor Store is a exclusive and horrible place to have a birthday party Friday nights." + {:small + "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/small.jpg", + :medium + "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/med.jpg", + :large + "http://cloudfront.net/ba5c2732-f133-41c7-b3aa-4b627710733b/large.jpg"} + {:name "Mission Free-Range Liquor Store", + :categories ["Free-Range" "Liquor Store"], + :phone "415-041-3816", + :id "6e665924-8e2c-42ab-af58-23a27f017e37"}] + [{:service "facebook", + :facebook-photo-id "7b6c29d2-078d-4feb-af95-88f10dc8937b", + :url + "http://facebook.com/photos/7b6c29d2-078d-4feb-af95-88f10dc8937b"} + "Rasta's Paleo Café is a delicious and delicious place to drink a craft beer in June." + {:small + "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/small.jpg", + :medium + "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/med.jpg", + :large + "http://cloudfront.net/c8302d26-6053-438a-9ac4-979699496d0f/large.jpg"} + {:name "Rasta's Paleo Café", + :categories ["Paleo" "Café"], + :phone "415-392-6341", + :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"}] + [{:service "twitter", + :mentions ["@mission_soul_food_pizzeria"], + :tags ["#soul" "#food" "#pizzeria"], + :username "cam_saul"} + "Mission Soul Food Pizzeria is a family-friendly and acceptable place to sip Champagne in June." + {:small + "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/small.jpg", + :medium + "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/med.jpg", + :large + "http://cloudfront.net/1ec9c182-6624-4444-8a7f-71c2058c0b0d/large.jpg"} + {:name "Mission Soul Food Pizzeria", + :categories ["Soul Food" "Pizzeria"], + :phone "415-437-3479", + :id "9905fe61-44cb-4626-843b-5d725c7949bb"}] + [{:service "flare", :username "bob"} + "Tenderloin Japanese Ice Cream Truck is a horrible and decent place to pitch an investor on Saturday night." + {:small + "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/small.jpg", + :medium + "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/med.jpg", + :large + "http://cloudfront.net/f818171e-344f-45e2-b0b7-d1ef9fc75866/large.jpg"} + {:name "Tenderloin Japanese Ice Cream Truck", + :categories ["Japanese" "Ice Cream Truck"], + :phone "415-856-0371", + :id "5ce47baa-bbef-4bc7-adf6-57842913ea8a"}] + [{:service "flare", :username "amy"} + "Tenderloin Paleo Hotel & Restaurant is a decent and great place to drink a craft beer on Thursdays." + {:small + "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/small.jpg", + :medium + "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/med.jpg", + :large + "http://cloudfront.net/33738a8e-ee82-45da-919d-e8739f7600e8/large.jpg"} + {:name "Tenderloin Paleo Hotel & Restaurant", + :categories ["Paleo" "Hotel & Restaurant"], + :phone "415-402-1652", + :id "4dea27b4-6d89-4b86-80a8-5631e171da8d"}] + [{:service "flare", :username "kyle"} + "Lucky's Low-Carb Coffee House is a horrible and historical place to take a date on a Tuesday afternoon." + {:small + "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/small.jpg", + :medium + "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/med.jpg", + :large + "http://cloudfront.net/e81790b7-b61d-4c97-a11d-d29414d5f3f6/large.jpg"} + {:name "Lucky's Low-Carb Coffee House", + :categories ["Low-Carb" "Coffee House"], + :phone "415-145-7107", + :id "81b0f944-f0ce-45e5-b84e-a924c441064a"}] + [{:service "flare", :username "bob"} + "Nob Hill Korean Taqueria is a atmospheric and horrible place to sip Champagne on Saturday night." + {:small + "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/small.jpg", + :medium + "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/med.jpg", + :large + "http://cloudfront.net/6444cc6c-ec22-4e3c-acfe-d1cf028d85b4/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "foursquare", + :foursquare-photo-id "0bb00482-d502-4381-9efb-975487a53dc8", + :mayor "cam_saul"} + "Rasta's Mexican Sushi is a popular and amazing place to nurse a hangover on a Tuesday afternoon." + {:small + "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/small.jpg", + :medium + "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/med.jpg", + :large + "http://cloudfront.net/586597a8-4d55-4d75-9146-55988033886c/large.jpg"} + {:name "Rasta's Mexican Sushi", + :categories ["Mexican" "Sushi"], + :phone "415-387-1284", + :id "e4912a22-e6ac-4806-8377-6497bf533a21"}] + [{:service "foursquare", + :foursquare-photo-id "9c94e761-e464-419e-8696-4670f1d9672c", + :mayor "amy"} + "Haight Mexican Restaurant is a popular and underground place to drink a craft beer with your pet dog." + {:small + "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/small.jpg", + :medium + "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/med.jpg", + :large + "http://cloudfront.net/62670450-c60f-4292-b667-4aed5e1ea266/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "flare", :username "jessica"} + "SoMa British Bakery is a swell and fantastic place to take visiting friends and relatives on Saturday night." + {:small + "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/small.jpg", + :medium + "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/med.jpg", + :large + "http://cloudfront.net/4dc5fa04-8931-456e-b05c-3056fb4b4678/large.jpg"} + {:name "SoMa British Bakery", + :categories ["British" "Bakery"], + :phone "415-909-5728", + :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"}] + [{:service "twitter", + :mentions ["@market_st._gluten_free_café"], + :tags ["#gluten-free" "#café"], + :username "mandy"} + "Market St. Gluten-Free Café is a historical and acceptable place to meet new friends during winter." + {:small + "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/small.jpg", + :medium + "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/med.jpg", + :large + "http://cloudfront.net/69b12faf-ddb6-491a-986b-a432c44ec881/large.jpg"} + {:name "Market St. Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-697-9776", + :id "ce4947d0-071a-4491-b5ac-f8b0241bd54c"}] + [{:service "twitter", + :mentions ["@haight_soul_food_hotel_&_restaurant"], + :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], + :username "joe"} + "Haight Soul Food Hotel & Restaurant is a amazing and underground place to watch the Warriors game in the spring." + {:small + "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/small.jpg", + :medium + "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/med.jpg", + :large + "http://cloudfront.net/8ed4e7fd-b105-4b49-8785-01fb01b56408/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "yelp", + :yelp-photo-id "d6adf74a-ac43-4a0d-8895-8a026f4577d6", + :categories ["Chinese" "Gastro Pub"]} + "Haight Chinese Gastro Pub is a world-famous and popular place to conduct a business meeting in July." + {:small + "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/small.jpg", + :medium + "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/med.jpg", + :large + "http://cloudfront.net/65330afd-5c4c-4647-87c6-ac687ea7b542/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "yelp", + :yelp-photo-id "c4683200-8e6c-4e0f-801b-8b5c69263c82", + :categories ["Red White & Blue" "Café"]} + "Polk St. Red White & Blue Café is a groovy and world-famous place to nurse a hangover on Taco Tuesday." + {:small + "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/small.jpg", + :medium + "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/med.jpg", + :large + "http://cloudfront.net/74df80a0-ead8-4389-a23c-b7126f5898fe/large.jpg"} + {:name "Polk St. Red White & Blue Café", + :categories ["Red White & Blue" "Café"], + :phone "415-986-0661", + :id "6eb9d2db-5015-49f0-bd18-f4c4938b7e5a"}] + [{:service "facebook", + :facebook-photo-id "0a9c5d13-ab77-4b81-9404-78519055f74f", + :url + "http://facebook.com/photos/0a9c5d13-ab77-4b81-9404-78519055f74f"} + "Rasta's Old-Fashioned Pop-Up Food Stand is a modern and great place to have a after-work cocktail after baseball games." + {:small + "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/small.jpg", + :medium + "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/med.jpg", + :large + "http://cloudfront.net/104a8586-58c9-4591-a46e-0b37a6a22078/large.jpg"} + {:name "Rasta's Old-Fashioned Pop-Up Food Stand", + :categories ["Old-Fashioned" "Pop-Up Food Stand"], + :phone "415-942-1875", + :id "9fd8b920-a877-4888-86bf-578b2724ac4e"}] + [{:service "flare", :username "lucky_pigeon"} + "Rasta's European Taqueria is a acceptable and overrated place to have brunch during winter." + {:small + "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/small.jpg", + :medium + "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/med.jpg", + :large + "http://cloudfront.net/62cbd734-78f4-4309-afbd-4399f6c19325/large.jpg"} + {:name "Rasta's European Taqueria", + :categories ["European" "Taqueria"], + :phone "415-631-1599", + :id "cb472880-ee6e-46e3-bd58-22cf33109aba"}] + [{:service "facebook", + :facebook-photo-id "38bdb459-90a7-45d2-815b-ac50f4b945b2", + :url + "http://facebook.com/photos/38bdb459-90a7-45d2-815b-ac50f4b945b2"} + "Haight Soul Food Café is a overrated and historical place to watch the Warriors game when hungover." + {:small + "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/small.jpg", + :medium + "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/med.jpg", + :large + "http://cloudfront.net/85edb88a-a7b8-4c77-aa67-cb937406f07e/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "yelp", + :yelp-photo-id "28eec82b-1a47-4e75-970f-ae118d14fcc0", + :categories ["Cage-Free" "Coffee House"]} + "Lower Pac Heights Cage-Free Coffee House is a exclusive and amazing place to pitch an investor with your pet dog." + {:small + "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/small.jpg", + :medium + "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/med.jpg", + :large + "http://cloudfront.net/2d605283-ac21-4fbb-8fcc-b0c237d95998/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "foursquare", + :foursquare-photo-id "7ad05d8f-924b-4e77-8222-54704df41a83", + :mayor "amy"} + "Haight Gormet Pizzeria is a overrated and popular place to have a after-work cocktail with your pet dog." + {:small + "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/small.jpg", + :medium + "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/med.jpg", + :large + "http://cloudfront.net/36ea3dc3-e9f5-4306-8b38-267eb0080754/large.jpg"} + {:name "Haight Gormet Pizzeria", + :categories ["Gormet" "Pizzeria"], + :phone "415-869-2197", + :id "0425bdd0-3f57-4108-80e3-78335327355a"}] + [{:service "facebook", + :facebook-photo-id "e149be3f-de3b-4f90-a3d5-fa5a51362a45", + :url + "http://facebook.com/photos/e149be3f-de3b-4f90-a3d5-fa5a51362a45"} + "SF Deep-Dish Eatery is a exclusive and classic place to have a after-work cocktail the first Sunday of the month." + {:small + "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/small.jpg", + :medium + "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/med.jpg", + :large + "http://cloudfront.net/ade9dbce-8bef-4aa4-836c-3dc0ef024976/large.jpg"} + {:name "SF Deep-Dish Eatery", + :categories ["Deep-Dish" "Eatery"], + :phone "415-476-9257", + :id "ad41d3f6-c20c-46a7-9e5d-db602fff7d0d"}] + [{:service "yelp", + :yelp-photo-id "75345d37-3a17-48ac-926c-707fdd1ed073", + :categories ["Soul Food" "Ice Cream Truck"]} + "Cam's Soul Food Ice Cream Truck is a overrated and underground place to sip a glass of expensive wine on public holidays." + {:small + "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/small.jpg", + :medium + "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/med.jpg", + :large + "http://cloudfront.net/30c66e10-c0dc-4439-9d3a-485ebec2b984/large.jpg"} + {:name "Cam's Soul Food Ice Cream Truck", + :categories ["Soul Food" "Ice Cream Truck"], + :phone "415-270-8888", + :id "f474e587-1801-43ea-93d5-4c4fd96460b8"}] + [{:service "twitter", + :mentions ["@haight_soul_food_hotel_&_restaurant"], + :tags ["#soul" "#food" "#hotel" "#&" "#restaurant"], + :username "jane"} + "Haight Soul Food Hotel & Restaurant is a horrible and underappreciated place to have brunch during summer." + {:small + "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/small.jpg", + :medium + "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/med.jpg", + :large + "http://cloudfront.net/80b32b4f-9d75-4494-bec3-0e4d1c77e592/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "facebook", + :facebook-photo-id "7c8ab6a5-f832-4d16-9f5a-e5bb64991845", + :url + "http://facebook.com/photos/7c8ab6a5-f832-4d16-9f5a-e5bb64991845"} + "Kyle's Japanese Hotel & Restaurant is a well-decorated and underappreciated place to drink a craft beer weekday afternoons." + {:small + "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/small.jpg", + :medium + "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/med.jpg", + :large + "http://cloudfront.net/375174fb-6d46-4034-aa46-e69d8501c16f/large.jpg"} + {:name "Kyle's Japanese Hotel & Restaurant", + :categories ["Japanese" "Hotel & Restaurant"], + :phone "415-337-5387", + :id "eced4f41-b627-4553-a297-888871038b69"}] + [{:service "flare", :username "rasta_toucan"} + "Sameer's GMO-Free Restaurant is a underappreciated and well-decorated place to take a date Friday nights." + {:small + "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/small.jpg", + :medium + "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/med.jpg", + :large + "http://cloudfront.net/f513fc07-1819-4e27-8459-621ee8daa89b/large.jpg"} + {:name "Sameer's GMO-Free Restaurant", + :categories ["GMO-Free" "Restaurant"], + :phone "415-128-9430", + :id "7ac8a7dd-c07f-45a6-92ba-bdb1b1280af2"}] + [{:service "flare", :username "cam_saul"} + "Kyle's Free-Range Taqueria is a groovy and classic place to pitch an investor the second Saturday of the month." + {:small + "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/small.jpg", + :medium + "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/med.jpg", + :large + "http://cloudfront.net/3496607d-7f71-4f4d-a3fb-38a93cb7dc9f/large.jpg"} + {:name "Kyle's Free-Range Taqueria", + :categories ["Free-Range" "Taqueria"], + :phone "415-201-7832", + :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"}] + [{:service "yelp", + :yelp-photo-id "94ee2c9f-bfcc-462e-9893-6a740cd89edf", + :categories ["Gluten-Free" "Gastro Pub"]} + "Lucky's Gluten-Free Gastro Pub is a atmospheric and wonderful place to drink a craft beer on Thursdays." + {:small + "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/small.jpg", + :medium + "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/med.jpg", + :large + "http://cloudfront.net/5e5e3ec3-45af-41e3-90fe-aa4923bfff0c/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "twitter", + :mentions ["@cams_mexican_gastro_pub"], + :tags ["#mexican" "#gastro" "#pub"], + :username "cam_saul"} + "Cam's Mexican Gastro Pub is a fantastic and great place to nurse a hangover the second Saturday of the month." + {:small + "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/small.jpg", + :medium + "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/med.jpg", + :large + "http://cloudfront.net/1682e808-cd81-455c-a146-310951ee9a48/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "yelp", + :yelp-photo-id "3bd9b0c5-4bb3-492f-9f79-b95540dce0a5", + :categories ["Cage-Free" "Liquor Store"]} + "Marina Cage-Free Liquor Store is a classic and family-friendly place to watch the Giants game weekend evenings." + {:small + "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/small.jpg", + :medium + "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/med.jpg", + :large + "http://cloudfront.net/4ad99b2b-e8c3-4ee0-b689-4bf4e5f6afc4/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "facebook", + :facebook-photo-id "ed8f13c6-cb45-4dd5-ab2b-9e5261fe3e7b", + :url + "http://facebook.com/photos/ed8f13c6-cb45-4dd5-ab2b-9e5261fe3e7b"} + "Lucky's Cage-Free Liquor Store is a exclusive and classic place to catch a bite to eat with your pet dog." + {:small + "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/small.jpg", + :medium + "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/med.jpg", + :large + "http://cloudfront.net/ea6196c8-f2da-4765-9705-24e9d74314a0/large.jpg"} + {:name "Lucky's Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-341-3219", + :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"}] + [{:service "flare", :username "amy"} + "Tenderloin Cage-Free Sushi is a well-decorated and popular place to have brunch on Saturday night." + {:small + "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/small.jpg", + :medium + "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/med.jpg", + :large + "http://cloudfront.net/4e19c71c-f413-4e9e-a802-df85a3ea1ca8/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "twitter", + :mentions ["@soma_old_fashioned_pizzeria"], + :tags ["#old-fashioned" "#pizzeria"], + :username "bob"} + "SoMa Old-Fashioned Pizzeria is a decent and well-decorated place to have a birthday party after baseball games." + {:small + "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/small.jpg", + :medium + "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/med.jpg", + :large + "http://cloudfront.net/2e81d020-d65b-43ac-abad-32c74d5890b8/large.jpg"} + {:name "SoMa Old-Fashioned Pizzeria", + :categories ["Old-Fashioned" "Pizzeria"], + :phone "415-966-8856", + :id "deb8997b-734d-402b-a181-bd888214bc86"}] + [{:service "flare", :username "sameer"} + "Marina Homestyle Pop-Up Food Stand is a groovy and great place to conduct a business meeting with your pet toucan." + {:small + "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/small.jpg", + :medium + "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/med.jpg", + :large + "http://cloudfront.net/2cf13084-184a-4a3b-9c64-9732b00b2410/large.jpg"} + {:name "Marina Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-094-4567", + :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"}] + [{:service "foursquare", + :foursquare-photo-id "a06600a6-ab71-4c47-bf7d-9334f0de14a6", + :mayor "jessica"} + "Sameer's Pizza Liquor Store is a world-famous and underappreciated place to have breakfast the first Sunday of the month." + {:small + "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/small.jpg", + :medium + "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/med.jpg", + :large + "http://cloudfront.net/2805f64d-9ee0-41e7-b07e-cffe37193efb/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "yelp", + :yelp-photo-id "500335c8-a480-4d0a-b2e5-fd44f240c668", + :categories ["BBQ" "Churros"]} + "Mission BBQ Churros is a underground and overrated place to have breakfast Friday nights." + {:small + "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/small.jpg", + :medium + "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/med.jpg", + :large + "http://cloudfront.net/e2e3cd36-f1f4-4a23-b1db-166d87e4bba2/large.jpg"} + {:name "Mission BBQ Churros", + :categories ["BBQ" "Churros"], + :phone "415-406-5374", + :id "429ea81a-02c5-449f-bfa7-03a11b227f1f"}] + [{:service "foursquare", + :foursquare-photo-id "dab0b46c-7062-4a40-8098-290e126c47df", + :mayor "lucky_pigeon"} + "Kyle's Old-Fashioned Pop-Up Food Stand is a amazing and underappreciated place to catch a bite to eat with friends." + {:small + "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/small.jpg", + :medium + "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/med.jpg", + :large + "http://cloudfront.net/ee0eca87-45da-43e0-b8ad-15e80d4fd00a/large.jpg"} + {:name "Kyle's Old-Fashioned Pop-Up Food Stand", + :categories ["Old-Fashioned" "Pop-Up Food Stand"], + :phone "415-638-8972", + :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"}] + [{:service "flare", :username "rasta_toucan"} + "Nob Hill Free-Range Ice Cream Truck is a decent and classic place to meet new friends during winter." + {:small + "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/small.jpg", + :medium + "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/med.jpg", + :large + "http://cloudfront.net/98aa0ae9-fc5c-4dad-a643-8d3415b7a95d/large.jpg"} + {:name "Nob Hill Free-Range Ice Cream Truck", + :categories ["Free-Range" "Ice Cream Truck"], + :phone "415-787-4049", + :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"}] + [{:service "foursquare", + :foursquare-photo-id "a061d2b6-8c85-4bf4-bc25-9450136340ca", + :mayor "rasta_toucan"} + "Haight Chinese Gastro Pub is a world-famous and acceptable place to have a birthday party with your pet dog." + {:small + "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/small.jpg", + :medium + "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/med.jpg", + :large + "http://cloudfront.net/faeeb454-e78f-4f14-84db-4b7acaafe5bb/large.jpg"} + {:name "Haight Chinese Gastro Pub", + :categories ["Chinese" "Gastro Pub"], + :phone "415-521-5825", + :id "12a8dc6e-1b2c-47e2-9c18-3ae220e4806f"}] + [{:service "foursquare", + :foursquare-photo-id "a45a7d9f-e866-4edf-9594-4229dc893000", + :mayor "biggie"} + "Sameer's Pizza Liquor Store is a world-famous and historical place to catch a bite to eat after baseball games." + {:small + "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/small.jpg", + :medium + "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/med.jpg", + :large + "http://cloudfront.net/ff6df82c-49f2-4061-9369-51ff3d043f61/large.jpg"} + {:name "Sameer's Pizza Liquor Store", + :categories ["Pizza" "Liquor Store"], + :phone "415-969-7474", + :id "7b9c7dc3-d8f1-498d-843a-e62360449892"}] + [{:service "facebook", + :facebook-photo-id "b02f088b-0b6e-4543-bb64-fc46173488c6", + :url + "http://facebook.com/photos/b02f088b-0b6e-4543-bb64-fc46173488c6"} + "Market St. Low-Carb Taqueria is a well-decorated and modern place to have breakfast weekday afternoons." + {:small + "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/small.jpg", + :medium + "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/med.jpg", + :large + "http://cloudfront.net/86ecd1d3-5d67-4ff2-9839-4c5858ac1013/large.jpg"} + {:name "Market St. Low-Carb Taqueria", + :categories ["Low-Carb" "Taqueria"], + :phone "415-751-6525", + :id "f30eb85b-f048-4d8c-8008-3c2876125061"}] + [{:service "twitter", + :mentions ["@joes_homestyle_eatery"], + :tags ["#homestyle" "#eatery"], + :username "jane"} + "Joe's Homestyle Eatery is a horrible and historical place to sip a glass of expensive wine Friday nights." + {:small + "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/small.jpg", + :medium + "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/med.jpg", + :large + "http://cloudfront.net/1f29c79b-7119-4f34-aec1-bc3863b6f392/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "flare", :username "cam_saul"} + "Market St. Homestyle Pop-Up Food Stand is a popular and delicious place to conduct a business meeting weekday afternoons." + {:small + "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/small.jpg", + :medium + "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/med.jpg", + :large + "http://cloudfront.net/6caf3094-8900-4461-8b3a-f6c43b3cea3d/large.jpg"} + {:name "Market St. Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-213-3030", + :id "2d873280-e43d-449e-9940-af96ae7df718"}] + [{:service "facebook", + :facebook-photo-id "97487fd1-61b3-4492-9d4f-45ceba863252", + :url + "http://facebook.com/photos/97487fd1-61b3-4492-9d4f-45ceba863252"} + "Lucky's Afgan Sushi is a historical and fantastic place to catch a bite to eat in the spring." + {:small + "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/small.jpg", + :medium + "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/med.jpg", + :large + "http://cloudfront.net/f3fb00c6-96fe-4b76-ba66-118e26328fd6/large.jpg"} + {:name "Lucky's Afgan Sushi", + :categories ["Afgan" "Sushi"], + :phone "415-188-3506", + :id "4a47d0d2-0123-4bb9-b941-38702f0697e9"}] + [{:service "flare", :username "tupac"} + "Market St. Homestyle Pop-Up Food Stand is a overrated and wonderful place to drink a craft beer with your pet toucan." + {:small + "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/small.jpg", + :medium + "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/med.jpg", + :large + "http://cloudfront.net/af633118-4ae8-450f-b48b-d02d86913e22/large.jpg"} + {:name "Market St. Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-213-3030", + :id "2d873280-e43d-449e-9940-af96ae7df718"}] + [{:service "twitter", + :mentions ["@sunset_homestyle_grill"], + :tags ["#homestyle" "#grill"], + :username "mandy"} + "Sunset Homestyle Grill is a acceptable and classic place to meet new friends in July." + {:small + "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/small.jpg", + :medium + "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/med.jpg", + :large + "http://cloudfront.net/e495076c-d822-460c-8e1e-eb76d9378433/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "twitter", + :mentions ["@polk_st._mexican_coffee_house"], + :tags ["#mexican" "#coffee" "#house"], + :username "rasta_toucan"} + "Polk St. Mexican Coffee House is a overrated and great place to meet new friends on Thursdays." + {:small + "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/small.jpg", + :medium + "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/med.jpg", + :large + "http://cloudfront.net/7f2cad4a-5bae-4860-af42-59af2b94c90f/large.jpg"} + {:name "Polk St. Mexican Coffee House", + :categories ["Mexican" "Coffee House"], + :phone "415-144-7901", + :id "396d36d7-13ad-41fd-86b5-8b70b6ecdabf"}] + [{:service "facebook", + :facebook-photo-id "83ad3825-1e95-4078-9a3e-902802396114", + :url + "http://facebook.com/photos/83ad3825-1e95-4078-9a3e-902802396114"} + "Marina Low-Carb Food Truck is a exclusive and acceptable place to have breakfast on a Tuesday afternoon." + {:small + "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/small.jpg", + :medium + "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/med.jpg", + :large + "http://cloudfront.net/b4147277-a95e-4c14-b8de-cdd330292af2/large.jpg"} + {:name "Marina Low-Carb Food Truck", + :categories ["Low-Carb" "Food Truck"], + :phone "415-748-3513", + :id "a13a5beb-19de-40ca-a334-02df3bdf5285"}] + [{:service "foursquare", + :foursquare-photo-id "ade689fc-821b-4c22-8e66-cc48a2e4d7b9", + :mayor "sameer"} + "Polk St. Korean Taqueria is a family-friendly and amazing place to conduct a business meeting the second Saturday of the month." + {:small + "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/small.jpg", + :medium + "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/med.jpg", + :large + "http://cloudfront.net/9eb15f11-015b-497c-86ee-ad3680ab00a8/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "facebook", + :facebook-photo-id "e6a59e30-0ffd-4a6f-b4bc-f45ba8c214cf", + :url + "http://facebook.com/photos/e6a59e30-0ffd-4a6f-b4bc-f45ba8c214cf"} + "Kyle's Japanese Hotel & Restaurant is a world-famous and world-famous place to have a drink Friday nights." + {:small + "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/small.jpg", + :medium + "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/med.jpg", + :large + "http://cloudfront.net/8a2fc34f-49ce-4705-85f7-94d0f5847656/large.jpg"} + {:name "Kyle's Japanese Hotel & Restaurant", + :categories ["Japanese" "Hotel & Restaurant"], + :phone "415-337-5387", + :id "eced4f41-b627-4553-a297-888871038b69"}] + [{:service "yelp", + :yelp-photo-id "0e7ee5ce-7a90-4431-959f-948658ec504c", + :categories ["Soul Food" "Pizzeria"]} + "Mission Soul Food Pizzeria is a swell and swell place to pitch an investor with your pet toucan." + {:small + "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/small.jpg", + :medium + "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/med.jpg", + :large + "http://cloudfront.net/108c11d1-e973-45c2-8c99-86ae460dfb1d/large.jpg"} + {:name "Mission Soul Food Pizzeria", + :categories ["Soul Food" "Pizzeria"], + :phone "415-437-3479", + :id "9905fe61-44cb-4626-843b-5d725c7949bb"}] + [{:service "foursquare", + :foursquare-photo-id "645a21e2-d948-4e57-a2c2-a29c6a6a32d8", + :mayor "lucky_pigeon"} + "Alcatraz Pizza Churros is a fantastic and world-famous place to watch the Giants game in the spring." + {:small + "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/small.jpg", + :medium + "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/med.jpg", + :large + "http://cloudfront.net/c97060df-c900-4330-bc2d-8277ca9844b2/large.jpg"} + {:name "Alcatraz Pizza Churros", + :categories ["Pizza" "Churros"], + :phone "415-754-7867", + :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"}] + [{:service "twitter", + :mentions ["@marina_no_msg_sushi"], + :tags ["#no-msg" "#sushi"], + :username "sameer"} + "Marina No-MSG Sushi is a wonderful and family-friendly place to have a birthday party in July." + {:small + "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/small.jpg", + :medium + "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/med.jpg", + :large + "http://cloudfront.net/7ad88c11-910e-4e48-b42c-3a2050f6f52b/large.jpg"} + {:name "Marina No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-856-5937", + :id "d51013a3-8547-4705-a5f0-cb11d8206481"}] + [{:service "flare", :username "tupac"} + "Haight Soul Food Sushi is a groovy and historical place to have a drink weekend evenings." + {:small + "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/small.jpg", + :medium + "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/med.jpg", + :large + "http://cloudfront.net/87a2f10c-18f9-4382-ab09-c5c8b92835b9/large.jpg"} + {:name "Haight Soul Food Sushi", + :categories ["Soul Food" "Sushi"], + :phone "415-371-8026", + :id "b4df5eb7-d8cd-431d-9d43-381984ec81ae"}] + [{:service "yelp", + :yelp-photo-id "e60013b6-4660-4767-8500-d55ccf154362", + :categories ["British" "Pop-Up Food Stand"]} + "SF British Pop-Up Food Stand is a horrible and amazing place to drink a craft beer with your pet dog." + {:small + "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/small.jpg", + :medium + "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/med.jpg", + :large + "http://cloudfront.net/25cf609f-25e8-4575-9ce0-e3037368fc53/large.jpg"} + {:name "SF British Pop-Up Food Stand", + :categories ["British" "Pop-Up Food Stand"], + :phone "415-441-3725", + :id "19eac087-7b1c-4668-a26c-d7c02cbcd3f6"}] + [{:service "flare", :username "sameer"} + "Haight Soul Food Café is a amazing and family-friendly place to take visiting friends and relatives on Saturday night." + {:small + "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/small.jpg", + :medium + "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/med.jpg", + :large + "http://cloudfront.net/a6b38881-d547-43f5-8a6f-429b73c92321/large.jpg"} + {:name "Haight Soul Food Café", + :categories ["Soul Food" "Café"], + :phone "415-257-1769", + :id "a1796c4b-da2b-474f-9fd6-4fa96c1eac70"}] + [{:service "flare", :username "rasta_toucan"} + "Oakland European Liquor Store is a wonderful and underappreciated place to people-watch in July." + {:small + "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/small.jpg", + :medium + "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/med.jpg", + :large + "http://cloudfront.net/09b212d7-4cfe-42d7-92bd-2be6b0c4239a/large.jpg"} + {:name "Oakland European Liquor Store", + :categories ["European" "Liquor Store"], + :phone "415-559-1516", + :id "e342e7b7-e82d-475d-a822-b2df9c84850d"}] + [{:service "flare", :username "jessica"} + "Nob Hill Free-Range Ice Cream Truck is a atmospheric and world-famous place to have brunch on Saturday night." + {:small + "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/small.jpg", + :medium + "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/med.jpg", + :large + "http://cloudfront.net/fcf20e3b-3966-467a-9a91-0a1be39e0c86/large.jpg"} + {:name "Nob Hill Free-Range Ice Cream Truck", + :categories ["Free-Range" "Ice Cream Truck"], + :phone "415-787-4049", + :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"}] + [{:service "facebook", + :facebook-photo-id "1be63daf-2a0c-4514-a7a3-d20264346b9d", + :url + "http://facebook.com/photos/1be63daf-2a0c-4514-a7a3-d20264346b9d"} + "Lucky's Gluten-Free Gastro Pub is a family-friendly and family-friendly place to watch the Warriors game weekday afternoons." + {:small + "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/small.jpg", + :medium + "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/med.jpg", + :large + "http://cloudfront.net/4929ecb6-e987-4a98-be9b-aa01ace293b1/large.jpg"} + {:name "Lucky's Gluten-Free Gastro Pub", + :categories ["Gluten-Free" "Gastro Pub"], + :phone "415-391-6443", + :id "7ccf8bbb-74b4-48f7-aa7c-43872f63cb1b"}] + [{:service "yelp", + :yelp-photo-id "2852b37d-d922-4259-be12-744fa4bfee02", + :categories ["American" "Churros"]} + "Sunset American Churros is a well-decorated and horrible place to watch the Giants game on public holidays." + {:small + "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/small.jpg", + :medium + "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/med.jpg", + :large + "http://cloudfront.net/98e6d78e-7926-44d8-b94d-12fa9903485c/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "yelp", + :yelp-photo-id "c151203a-b7d1-45c7-89c5-f874054cb524", + :categories ["European" "Churros"]} + "Kyle's European Churros is a family-friendly and fantastic place to have brunch in July." + {:small + "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/small.jpg", + :medium + "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/med.jpg", + :large + "http://cloudfront.net/33370b35-d26b-48be-a6f0-135d09d82dd6/large.jpg"} + {:name "Kyle's European Churros", + :categories ["European" "Churros"], + :phone "415-233-8392", + :id "5270240c-6e6e-4512-9344-3dc497d6ea49"}] + [{:service "twitter", + :mentions ["@tenderloin_cage_free_sushi"], + :tags ["#cage-free" "#sushi"], + :username "sameer"} + "Tenderloin Cage-Free Sushi is a world-famous and underground place to drink a craft beer on a Tuesday afternoon." + {:small + "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/small.jpg", + :medium + "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/med.jpg", + :large + "http://cloudfront.net/6ec6cac2-7492-4432-af7f-291301d03031/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "foursquare", + :foursquare-photo-id "7efa0b90-41f8-42b4-b148-b981eb3d2c0f", + :mayor "cam_saul"} + "Kyle's Japanese Hotel & Restaurant is a wonderful and decent place to catch a bite to eat with friends." + {:small + "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/small.jpg", + :medium + "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/med.jpg", + :large + "http://cloudfront.net/d4806e59-38d0-4dc2-87b9-49c4ef406fd1/large.jpg"} + {:name "Kyle's Japanese Hotel & Restaurant", + :categories ["Japanese" "Hotel & Restaurant"], + :phone "415-337-5387", + :id "eced4f41-b627-4553-a297-888871038b69"}] + [{:service "foursquare", + :foursquare-photo-id "87cbe196-2aa6-45d1-87b8-bf9027c655c1", + :mayor "cam_saul"} + "Haight Soul Food Pop-Up Food Stand is a decent and atmospheric place to nurse a hangover weekend mornings." + {:small + "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/small.jpg", + :medium + "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/med.jpg", + :large + "http://cloudfront.net/73c98d12-5bf9-40a1-b119-708f84d0fc74/large.jpg"} + {:name "Haight Soul Food Pop-Up Food Stand", + :categories ["Soul Food" "Pop-Up Food Stand"], + :phone "415-741-8726", + :id "9735184b-1299-410f-a98e-10d9c548af42"}] + [{:service "facebook", + :facebook-photo-id "07b3bf42-0187-4f24-9d2a-b6cb5419fe6b", + :url + "http://facebook.com/photos/07b3bf42-0187-4f24-9d2a-b6cb5419fe6b"} + "Tenderloin Gluten-Free Bar & Grill is a popular and wonderful place to sip Champagne on Saturday night." + {:small + "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/small.jpg", + :medium + "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/med.jpg", + :large + "http://cloudfront.net/03a096b6-2326-4e11-9326-29793d4a03d5/large.jpg"} + {:name "Tenderloin Gluten-Free Bar & Grill", + :categories ["Gluten-Free" "Bar & Grill"], + :phone "415-904-0956", + :id "0d7e235a-eea8-45b3-aaa7-23b4ea2b50f2"}] + [{:service "yelp", + :yelp-photo-id "d8cfa65d-018c-4a07-baa5-ef38e019b53e", + :categories ["Deep-Dish" "Ice Cream Truck"]} + "Lower Pac Heights Deep-Dish Ice Cream Truck is a amazing and family-friendly place to have a birthday party Friday nights." + {:small + "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/small.jpg", + :medium + "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/med.jpg", + :large + "http://cloudfront.net/a93dc0cc-7974-4a90-a899-fdcd468c5f8d/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Ice Cream Truck", + :categories ["Deep-Dish" "Ice Cream Truck"], + :phone "415-495-1414", + :id "d5efa2f2-496d-41b2-8b85-3d002a22a2bc"}] + [{:service "yelp", + :yelp-photo-id "e73b3ca1-da16-42f7-99ed-11e978abae41", + :categories ["American" "Churros"]} + "Sunset American Churros is a historical and swell place to catch a bite to eat on a Tuesday afternoon." + {:small + "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/small.jpg", + :medium + "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/med.jpg", + :large + "http://cloudfront.net/f7e89230-3e54-487f-aa62-c941fa30e833/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "foursquare", + :foursquare-photo-id "abf79d51-a26b-48f8-8267-28af692327b7", + :mayor "lucky_pigeon"} + "Kyle's Low-Carb Grill is a decent and great place to watch the Warriors game in the fall." + {:small + "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/small.jpg", + :medium + "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/med.jpg", + :large + "http://cloudfront.net/b24e4e79-6771-4b12-b614-eb702200e544/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "facebook", + :facebook-photo-id "24bc49af-3dd9-4d57-bd9c-204aacd68eea", + :url + "http://facebook.com/photos/24bc49af-3dd9-4d57-bd9c-204aacd68eea"} + "Pacific Heights No-MSG Sushi is a world-famous and groovy place to have breakfast during summer." + {:small + "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/small.jpg", + :medium + "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/med.jpg", + :large + "http://cloudfront.net/3ee22abf-acd2-433f-9d18-00747bb907b3/large.jpg"} + {:name "Pacific Heights No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-354-9547", + :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"}] + [{:service "foursquare", + :foursquare-photo-id "c57cddd8-4867-43cf-8f71-892e87788b73", + :mayor "jessica"} + "Lower Pac Heights Deep-Dish Liquor Store is a delicious and underappreciated place to have a after-work cocktail in the fall." + {:small + "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/small.jpg", + :medium + "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/med.jpg", + :large + "http://cloudfront.net/585e25e5-5ae1-403b-acdf-a5069173d053/large.jpg"} + {:name "Lower Pac Heights Deep-Dish Liquor Store", + :categories ["Deep-Dish" "Liquor Store"], + :phone "415-497-3039", + :id "4d4eabfc-ff1f-4bc6-88b0-2f55489ff666"}] + [{:service "twitter", + :mentions ["@pacific_heights_free_range_eatery"], + :tags ["#free-range" "#eatery"], + :username "mandy"} + "Pacific Heights Free-Range Eatery is a decent and acceptable place to take visiting friends and relatives Friday nights." + {:small + "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/small.jpg", + :medium + "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/med.jpg", + :large + "http://cloudfront.net/d0f3968e-c660-45eb-9d17-bcd3b9229956/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "yelp", + :yelp-photo-id "6301ba32-41f7-44ff-872e-3c6f96e34ebb", + :categories ["Cage-Free" "Liquor Store"]} + "Marina Cage-Free Liquor Store is a popular and wonderful place to take visiting friends and relatives weekend mornings." + {:small + "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/small.jpg", + :medium + "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/med.jpg", + :large + "http://cloudfront.net/88852ac8-d7e1-4cba-b4e3-ec810088b8a9/large.jpg"} + {:name "Marina Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-571-0783", + :id "ad68f549-3000-407e-ab98-7f314cfa4653"}] + [{:service "yelp", + :yelp-photo-id "7c9bc27a-7478-4bec-b6e1-99657346b360", + :categories ["Japanese" "Coffee House"]} + "Mission Japanese Coffee House is a popular and swell place to have a drink on public holidays." + {:small + "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/small.jpg", + :medium + "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/med.jpg", + :large + "http://cloudfront.net/5b8270f9-29aa-4d47-8a64-23b26dc7e688/large.jpg"} + {:name "Mission Japanese Coffee House", + :categories ["Japanese" "Coffee House"], + :phone "415-561-0506", + :id "60dd274e-0cbf-4521-946d-8a4e0f151150"}] + [{:service "twitter", + :mentions ["@joes_homestyle_eatery"], + :tags ["#homestyle" "#eatery"], + :username "sameer"} + "Joe's Homestyle Eatery is a swell and groovy place to take a date in June." + {:small + "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/small.jpg", + :medium + "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/med.jpg", + :large + "http://cloudfront.net/6b7c484a-4839-4f61-b32d-690fbffe08d8/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "twitter", + :mentions ["@nob_hill_free_range_ice_cream_truck"], + :tags ["#free-range" "#ice" "#cream" "#truck"], + :username "jane"} + "Nob Hill Free-Range Ice Cream Truck is a well-decorated and modern place to watch the Giants game after baseball games." + {:small + "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/small.jpg", + :medium + "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/med.jpg", + :large + "http://cloudfront.net/f2c105a6-db11-44c4-a0a7-5adac712a5f1/large.jpg"} + {:name "Nob Hill Free-Range Ice Cream Truck", + :categories ["Free-Range" "Ice Cream Truck"], + :phone "415-787-4049", + :id "08d1e93c-105f-4abf-a9ec-b2e3cd30747e"}] + [{:service "facebook", + :facebook-photo-id "f37c703b-0e19-4b91-9fc0-3945524300ed", + :url + "http://facebook.com/photos/f37c703b-0e19-4b91-9fc0-3945524300ed"} + "Lucky's Gluten-Free Café is a horrible and historical place to conduct a business meeting on Saturday night." + {:small + "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/small.jpg", + :medium + "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/med.jpg", + :large + "http://cloudfront.net/e1af0686-6fdb-415a-baa1-2e076dd1af2a/large.jpg"} + {:name "Lucky's Gluten-Free Café", + :categories ["Gluten-Free" "Café"], + :phone "415-740-2328", + :id "379af987-ad40-4a93-88a6-0233e1c14649"}] + [{:service "flare", :username "kyle"} + "Kyle's Low-Carb Grill is a decent and underground place to have brunch on Taco Tuesday." + {:small + "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/small.jpg", + :medium + "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/med.jpg", + :large + "http://cloudfront.net/b29b4f66-ef21-47e1-be7e-a3cd319c2110/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "flare", :username "amy"} + "Rasta's Paleo Churros is a popular and family-friendly place to conduct a business meeting on a Tuesday afternoon." + {:small + "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/small.jpg", + :medium + "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/med.jpg", + :large + "http://cloudfront.net/ab6f99aa-a1b9-4f3b-904a-fdc16c363827/large.jpg"} + {:name "Rasta's Paleo Churros", + :categories ["Paleo" "Churros"], + :phone "415-915-0309", + :id "3bf48ec6-434b-43b1-be28-7644975ecaf9"}] + [{:service "foursquare", + :foursquare-photo-id "e99162db-5fc6-4164-9537-a35aee30f51c", + :mayor "kyle"} + "Kyle's Old-Fashioned Pop-Up Food Stand is a world-famous and family-friendly place to have a drink with your pet dog." + {:small + "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/small.jpg", + :medium + "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/med.jpg", + :large + "http://cloudfront.net/7333d3c2-37c6-4a9d-96ab-019ac1174b91/large.jpg"} + {:name "Kyle's Old-Fashioned Pop-Up Food Stand", + :categories ["Old-Fashioned" "Pop-Up Food Stand"], + :phone "415-638-8972", + :id "7da187e8-bd01-48ca-ad93-7a02a442d9eb"}] + [{:service "facebook", + :facebook-photo-id "87163e8d-0bcf-4d20-b3a8-ab895ddfef15", + :url + "http://facebook.com/photos/87163e8d-0bcf-4d20-b3a8-ab895ddfef15"} + "Joe's Homestyle Eatery is a horrible and exclusive place to watch the Giants game the first Sunday of the month." + {:small + "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/small.jpg", + :medium + "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/med.jpg", + :large + "http://cloudfront.net/bd701a19-53c9-4cc2-aecd-f1e3c499493e/large.jpg"} + {:name "Joe's Homestyle Eatery", + :categories ["Homestyle" "Eatery"], + :phone "415-950-1337", + :id "5cc18489-dfaf-417b-900f-5d1d61b961e8"}] + [{:service "foursquare", + :foursquare-photo-id "08c45fbc-d085-4e2d-8b69-3e2d89f457a9", + :mayor "amy"} + "Lucky's Deep-Dish Gastro Pub is a horrible and underground place to have a birthday party with your pet dog." + {:small + "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/small.jpg", + :medium + "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/med.jpg", + :large + "http://cloudfront.net/30ca52de-38cc-4194-9afb-f8879e55cbf5/large.jpg"} + {:name "Lucky's Deep-Dish Gastro Pub", + :categories ["Deep-Dish" "Gastro Pub"], + :phone "415-487-4085", + :id "0136c454-0968-41cd-a237-ceec5724cab8"}] + [{:service "twitter", + :mentions ["@sameers_gmo_free_pop_up_food_stand"], + :tags ["#gmo-free" "#pop-up" "#food" "#stand"], + :username "joe"} + "Sameer's GMO-Free Pop-Up Food Stand is a swell and world-famous place to take visiting friends and relatives during summer." + {:small + "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/small.jpg", + :medium + "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/med.jpg", + :large + "http://cloudfront.net/271d9185-e724-4afd-b3a2-b8d9eee096eb/large.jpg"} + {:name "Sameer's GMO-Free Pop-Up Food Stand", + :categories ["GMO-Free" "Pop-Up Food Stand"], + :phone "415-217-7891", + :id "a829efc7-7e03-4e73-b072-83d10d1e3953"}] + [{:service "twitter", + :mentions ["@nob_hill_korean_taqueria"], + :tags ["#korean" "#taqueria"], + :username "amy"} + "Nob Hill Korean Taqueria is a classic and classic place to take a date after baseball games." + {:small + "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/small.jpg", + :medium + "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/med.jpg", + :large + "http://cloudfront.net/8f8cfa52-8973-4af0-9226-1df9eda589d9/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "facebook", + :facebook-photo-id "ba479234-45d6-4e25-aba9-5f820f0ba318", + :url + "http://facebook.com/photos/ba479234-45d6-4e25-aba9-5f820f0ba318"} + "Lucky's Afgan Sushi is a swell and amazing place to drink a craft beer the second Saturday of the month." + {:small + "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/small.jpg", + :medium + "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/med.jpg", + :large + "http://cloudfront.net/77cdaadd-b53a-48f5-b434-8b9b2262e5cf/large.jpg"} + {:name "Lucky's Afgan Sushi", + :categories ["Afgan" "Sushi"], + :phone "415-188-3506", + :id "4a47d0d2-0123-4bb9-b941-38702f0697e9"}] + [{:service "foursquare", + :foursquare-photo-id "8c374317-71fe-4112-ab02-53450a771d48", + :mayor "joe"} + "Lucky's Deep-Dish Gastro Pub is a delicious and amazing place to sip a glass of expensive wine in June." + {:small + "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/small.jpg", + :medium + "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/med.jpg", + :large + "http://cloudfront.net/9c59add8-379d-4f9a-81a8-7346e88b25d5/large.jpg"} + {:name "Lucky's Deep-Dish Gastro Pub", + :categories ["Deep-Dish" "Gastro Pub"], + :phone "415-487-4085", + :id "0136c454-0968-41cd-a237-ceec5724cab8"}] + [{:service "flare", :username "cam_saul"} + "Pacific Heights Free-Range Eatery is a exclusive and wonderful place to pitch an investor in July." + {:small + "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/small.jpg", + :medium + "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/med.jpg", + :large + "http://cloudfront.net/fe3f8af1-a494-48af-b6c2-a4d16f99c506/large.jpg"} + {:name "Pacific Heights Free-Range Eatery", + :categories ["Free-Range" "Eatery"], + :phone "415-901-6541", + :id "88b361c8-ce69-4b2e-b0f2-9deedd574af6"}] + [{:service "twitter", + :mentions ["@tenderloin_cage_free_sushi"], + :tags ["#cage-free" "#sushi"], + :username "sameer"} + "Tenderloin Cage-Free Sushi is a fantastic and modern place to take a date during summer." + {:small + "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/small.jpg", + :medium + "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/med.jpg", + :large + "http://cloudfront.net/546084bf-adf8-4f19-9a77-09b06e67c07d/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "facebook", + :facebook-photo-id "4e3e3372-d489-4ec6-9b80-c3615d7bb110", + :url + "http://facebook.com/photos/4e3e3372-d489-4ec6-9b80-c3615d7bb110"} + "Polk St. Japanese Liquor Store is a delicious and decent place to drink a craft beer the first Sunday of the month." + {:small + "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/small.jpg", + :medium + "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/med.jpg", + :large + "http://cloudfront.net/3f9ee642-6113-4d6c-ba97-2e51be75474f/large.jpg"} + {:name "Polk St. Japanese Liquor Store", + :categories ["Japanese" "Liquor Store"], + :phone "415-726-7986", + :id "b57ceac5-328d-4b65-9909-a1f9abc93015"}] + [{:service "facebook", + :facebook-photo-id "e40f9858-278e-4b0f-aa49-57295cdad381", + :url + "http://facebook.com/photos/e40f9858-278e-4b0f-aa49-57295cdad381"} + "Sunset Homestyle Grill is a overrated and popular place to have a after-work cocktail weekday afternoons." + {:small + "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/small.jpg", + :medium + "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/med.jpg", + :large + "http://cloudfront.net/27bffcc4-0017-4759-aeb9-5b78d1bf7ec3/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "flare", :username "jane"} + "Cam's Old-Fashioned Coffee House is a groovy and popular place to conduct a business meeting weekend evenings." + {:small + "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/small.jpg", + :medium + "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/med.jpg", + :large + "http://cloudfront.net/ef480d7a-3256-450b-9631-b38e9848a3f3/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-868-2973", + :id "27592c2b-e682-44bb-be28-8e9a622becca"}] + [{:service "foursquare", + :foursquare-photo-id "a81b8c97-cde1-40d8-8a54-05fc2c6074b4", + :mayor "rasta_toucan"} + "Joe's Modern Coffee House is a swell and swell place to catch a bite to eat in the spring." + {:small + "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/small.jpg", + :medium + "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/med.jpg", + :large + "http://cloudfront.net/95a7cf90-5437-4f75-9bee-e61c08daca75/large.jpg"} + {:name "Joe's Modern Coffee House", + :categories ["Modern" "Coffee House"], + :phone "415-331-5269", + :id "cd9e3610-9ead-4f0b-ad93-cd53611d49fe"}] + [{:service "twitter", + :mentions ["@marina_homestyle_pop_up_food_stand"], + :tags ["#homestyle" "#pop-up" "#food" "#stand"], + :username "biggie"} + "Marina Homestyle Pop-Up Food Stand is a exclusive and modern place to have brunch the second Saturday of the month." + {:small + "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/small.jpg", + :medium + "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/med.jpg", + :large + "http://cloudfront.net/a9541f11-91c0-4a36-bb2f-5b41fcd7d6f9/large.jpg"} + {:name "Marina Homestyle Pop-Up Food Stand", + :categories ["Homestyle" "Pop-Up Food Stand"], + :phone "415-094-4567", + :id "88a7ae3c-8b36-4901-a0c5-b82342cba6cd"}] + [{:service "foursquare", + :foursquare-photo-id "c628a149-96fc-4ece-a89c-d07f0d57bc83", + :mayor "biggie"} + "Mission Japanese Coffee House is a amazing and decent place to catch a bite to eat on public holidays." + {:small + "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/small.jpg", + :medium + "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/med.jpg", + :large + "http://cloudfront.net/4af17c4d-013b-47b6-83af-46fce1f5ceba/large.jpg"} + {:name "Mission Japanese Coffee House", + :categories ["Japanese" "Coffee House"], + :phone "415-561-0506", + :id "60dd274e-0cbf-4521-946d-8a4e0f151150"}] + [{:service "foursquare", + :foursquare-photo-id "29bdfc7c-9766-463f-a8c0-4568fa583604", + :mayor "bob"} + "Kyle's Free-Range Taqueria is a underground and groovy place to have a drink during summer." + {:small + "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/small.jpg", + :medium + "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/med.jpg", + :large + "http://cloudfront.net/c7533c61-98b3-4592-b02c-15d34d4b2da6/large.jpg"} + {:name "Kyle's Free-Range Taqueria", + :categories ["Free-Range" "Taqueria"], + :phone "415-201-7832", + :id "7aeb9416-4fe6-45f9-8849-6b8ba6d3f3b9"}] + [{:service "facebook", + :facebook-photo-id "8a7c76a5-9099-4e2e-b6d8-8eebe10acf72", + :url + "http://facebook.com/photos/8a7c76a5-9099-4e2e-b6d8-8eebe10acf72"} + "Sameer's GMO-Free Pop-Up Food Stand is a delicious and family-friendly place to people-watch weekday afternoons." + {:small + "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/small.jpg", + :medium + "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/med.jpg", + :large + "http://cloudfront.net/dffdba53-41d7-4b09-bacd-9dacd98f576d/large.jpg"} + {:name "Sameer's GMO-Free Pop-Up Food Stand", + :categories ["GMO-Free" "Pop-Up Food Stand"], + :phone "415-217-7891", + :id "a829efc7-7e03-4e73-b072-83d10d1e3953"}] + [{:service "yelp", + :yelp-photo-id "ec651838-e2d7-45c1-8b8a-70f80f87fae5", + :categories ["Japanese" "Coffee House"]} + "Mission Japanese Coffee House is a underappreciated and delicious place to drink a craft beer on public holidays." + {:small + "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/small.jpg", + :medium + "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/med.jpg", + :large + "http://cloudfront.net/6326e639-414c-4a23-8997-d6165cf61ea5/large.jpg"} + {:name "Mission Japanese Coffee House", + :categories ["Japanese" "Coffee House"], + :phone "415-561-0506", + :id "60dd274e-0cbf-4521-946d-8a4e0f151150"}] + [{:service "twitter", + :mentions ["@cams_mexican_gastro_pub"], + :tags ["#mexican" "#gastro" "#pub"], + :username "kyle"} + "Cam's Mexican Gastro Pub is a historical and underappreciated place to conduct a business meeting with friends." + {:small + "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/small.jpg", + :medium + "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/med.jpg", + :large + "http://cloudfront.net/6e3a5256-275f-4056-b61a-25990b4bb484/large.jpg"} + {:name "Cam's Mexican Gastro Pub", + :categories ["Mexican" "Gastro Pub"], + :phone "415-320-9123", + :id "bb958ac5-758e-4f42-b984-6b0e13f25194"}] + [{:service "twitter", + :mentions ["@haight_mexican_restaurant"], + :tags ["#mexican" "#restaurant"], + :username "bob"} + "Haight Mexican Restaurant is a well-decorated and popular place to have breakfast Friday nights." + {:small + "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/small.jpg", + :medium + "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/med.jpg", + :large + "http://cloudfront.net/6c89c785-1e69-449f-9eb8-81f5adaa40a3/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "twitter", + :mentions ["@chinatown_paleo_food_truck"], + :tags ["#paleo" "#food" "#truck"], + :username "bob"} + "Chinatown Paleo Food Truck is a great and underappreciated place to take a date in the spring." + {:small + "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/small.jpg", + :medium + "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/med.jpg", + :large + "http://cloudfront.net/57814010-b3cc-424f-ba74-4e2cc39e838d/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "flare", :username "sameer"} + "Lucky's Cage-Free Liquor Store is a overrated and fantastic place to have a birthday party in the fall." + {:small + "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/small.jpg", + :medium + "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/med.jpg", + :large + "http://cloudfront.net/b1686642-2751-4c46-9bbf-cf37fdb35fd2/large.jpg"} + {:name "Lucky's Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-341-3219", + :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"}] + [{:service "facebook", + :facebook-photo-id "362ae2f8-3bca-4a80-8b34-708aaac74139", + :url + "http://facebook.com/photos/362ae2f8-3bca-4a80-8b34-708aaac74139"} + "Pacific Heights No-MSG Sushi is a horrible and horrible place to people-watch the second Saturday of the month." + {:small + "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/small.jpg", + :medium + "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/med.jpg", + :large + "http://cloudfront.net/dd186a13-fd25-4114-9f7e-ea2e045638b8/large.jpg"} + {:name "Pacific Heights No-MSG Sushi", + :categories ["No-MSG" "Sushi"], + :phone "415-354-9547", + :id "b15b66a8-f3da-4b65-8156-80f5518fdd5a"}] + [{:service "facebook", + :facebook-photo-id "8ab454e9-36ae-4824-a60b-bcacaaa56a76", + :url + "http://facebook.com/photos/8ab454e9-36ae-4824-a60b-bcacaaa56a76"} + "Alcatraz Pizza Churros is a swell and acceptable place to catch a bite to eat Friday nights." + {:small + "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/small.jpg", + :medium + "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/med.jpg", + :large + "http://cloudfront.net/59cd5ba5-a68d-4d65-b104-9c0b7cc090f3/large.jpg"} + {:name "Alcatraz Pizza Churros", + :categories ["Pizza" "Churros"], + :phone "415-754-7867", + :id "df95e4f1-8719-42af-a15d-3ee00de6e04f"}] + [{:service "flare", :username "amy"} + "Marina Modern Bar & Grill is a exclusive and world-famous place to catch a bite to eat weekend evenings." + {:small + "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/small.jpg", + :medium + "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/med.jpg", + :large + "http://cloudfront.net/98d2956f-a799-49c8-9e39-cefd74ae6280/large.jpg"} + {:name "Marina Modern Bar & Grill", + :categories ["Modern" "Bar & Grill"], + :phone "415-203-8530", + :id "806144f1-bb7a-4271-8fcb-fc6550f51676"}] + [{:service "foursquare", + :foursquare-photo-id "bf141876-b610-4fcb-a1c2-d292d8989929", + :mayor "amy"} + "Rasta's British Food Truck is a amazing and great place to sip Champagne on a Tuesday afternoon." + {:small + "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/small.jpg", + :medium + "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/med.jpg", + :large + "http://cloudfront.net/d0061644-021c-4ee1-a2e2-1c0da0c0348a/large.jpg"} + {:name "Rasta's British Food Truck", + :categories ["British" "Food Truck"], + :phone "415-958-9031", + :id "b6616c97-01d0-488f-a855-bcd6efe2b899"}] + [{:service "twitter", + :mentions ["@luckys_deep_dish_gastro_pub"], + :tags ["#deep-dish" "#gastro" "#pub"], + :username "cam_saul"} + "Lucky's Deep-Dish Gastro Pub is a classic and well-decorated place to have a birthday party in the spring." + {:small + "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/small.jpg", + :medium + "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/med.jpg", + :large + "http://cloudfront.net/eb4cef56-fac6-4407-b952-ed5da677a144/large.jpg"} + {:name "Lucky's Deep-Dish Gastro Pub", + :categories ["Deep-Dish" "Gastro Pub"], + :phone "415-487-4085", + :id "0136c454-0968-41cd-a237-ceec5724cab8"}] + [{:service "foursquare", + :foursquare-photo-id "f7875634-d317-4cb5-a04a-cf6babd14144", + :mayor "rasta_toucan"} + "Sameer's Chinese Restaurant is a underappreciated and popular place to watch the Warriors game Friday nights." + {:small + "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/small.jpg", + :medium + "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/med.jpg", + :large + "http://cloudfront.net/d201302e-4e74-4db8-84f2-bd3dec57c1d8/large.jpg"} + {:name "Sameer's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-707-3659", + :id "51a9545e-7e1e-40f1-b550-09067b648f20"}] + [{:service "foursquare", + :foursquare-photo-id "ffbd6d15-24cf-4dd3-a168-40ac03cd2f18", + :mayor "rasta_toucan"} + "Nob Hill Korean Taqueria is a well-decorated and underappreciated place to catch a bite to eat on Thursdays." + {:small + "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/small.jpg", + :medium + "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/med.jpg", + :large + "http://cloudfront.net/4e782847-17da-4f87-bc31-fb61f22f3928/large.jpg"} + {:name "Nob Hill Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-107-7332", + :id "a43c184c-90f5-488c-bb3b-00ea2666d90e"}] + [{:service "facebook", + :facebook-photo-id "9ecd820a-7017-4be2-a4b1-18ab0da5e233", + :url + "http://facebook.com/photos/9ecd820a-7017-4be2-a4b1-18ab0da5e233"} + "SoMa British Bakery is a fantastic and modern place to take a date on public holidays." + {:small + "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/small.jpg", + :medium + "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/med.jpg", + :large + "http://cloudfront.net/cda97264-0585-4313-91c5-2d022eae6048/large.jpg"} + {:name "SoMa British Bakery", + :categories ["British" "Bakery"], + :phone "415-909-5728", + :id "662cb0d0-8ee6-4db7-aaf1-89eb2530feda"}] + [{:service "foursquare", + :foursquare-photo-id "4a084c4a-884f-4907-9b96-390b543cb03f", + :mayor "biggie"} + "Haight Mexican Restaurant is a exclusive and swell place to nurse a hangover on a Tuesday afternoon." + {:small + "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/small.jpg", + :medium + "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/med.jpg", + :large + "http://cloudfront.net/a8b906e6-9b89-4e89-b997-1476ba37e137/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "facebook", + :facebook-photo-id "2f1c35f8-83fb-4c81-a92c-00a81d9c93de", + :url + "http://facebook.com/photos/2f1c35f8-83fb-4c81-a92c-00a81d9c93de"} + "Sameer's Chinese Restaurant is a horrible and well-decorated place to have a drink on public holidays." + {:small + "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/small.jpg", + :medium + "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/med.jpg", + :large + "http://cloudfront.net/658de6d7-a7c1-4130-a5e5-d65946445068/large.jpg"} + {:name "Sameer's Chinese Restaurant", + :categories ["Chinese" "Restaurant"], + :phone "415-707-3659", + :id "51a9545e-7e1e-40f1-b550-09067b648f20"}] + [{:service "foursquare", + :foursquare-photo-id "62fdb59c-0629-4adb-b4e1-6a6cb7c7e280", + :mayor "mandy"} + "Chinatown Paleo Food Truck is a groovy and decent place to drink a craft beer during summer." + {:small + "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/small.jpg", + :medium + "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/med.jpg", + :large + "http://cloudfront.net/8fc01dbf-d0ff-4a50-b460-6e92367ced21/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "foursquare", + :foursquare-photo-id "089ff188-9374-4b13-93e6-005c9767dabb", + :mayor "bob"} + "Rasta's Mexican Sushi is a acceptable and exclusive place to have a drink weekday afternoons." + {:small + "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/small.jpg", + :medium + "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/med.jpg", + :large + "http://cloudfront.net/74795ea3-50d8-450a-b82b-33e6a1bb2e2c/large.jpg"} + {:name "Rasta's Mexican Sushi", + :categories ["Mexican" "Sushi"], + :phone "415-387-1284", + :id "e4912a22-e6ac-4806-8377-6497bf533a21"}] + [{:service "yelp", + :yelp-photo-id "42494912-52c5-4492-83c8-76454f465d99", + :categories ["Mexican" "Restaurant"]} + "Haight Mexican Restaurant is a modern and groovy place to watch the Warriors game on a Tuesday afternoon." + {:small + "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/small.jpg", + :medium + "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/med.jpg", + :large + "http://cloudfront.net/5e14844c-74af-4fdd-861d-293877f4505c/large.jpg"} + {:name "Haight Mexican Restaurant", + :categories ["Mexican" "Restaurant"], + :phone "415-758-8690", + :id "a5e3f0ac-f6e8-4e71-a0a2-3f10f48b4ab1"}] + [{:service "facebook", + :facebook-photo-id "d326a510-ac21-46f3-8474-078ef511cc54", + :url + "http://facebook.com/photos/d326a510-ac21-46f3-8474-078ef511cc54"} + "Nob Hill Gluten-Free Coffee House is a horrible and classic place to people-watch during summer." + {:small + "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/small.jpg", + :medium + "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/med.jpg", + :large + "http://cloudfront.net/b3276e40-75ad-497a-a2d9-a3d3dfdf39cc/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "foursquare", + :foursquare-photo-id "1df360d1-5d63-4a28-9c37-966dc7e616f0", + :mayor "joe"} + "Mission British Café is a underground and groovy place to have brunch weekday afternoons." + {:small + "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/small.jpg", + :medium + "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/med.jpg", + :large + "http://cloudfront.net/67d2f053-4bf1-43da-9135-0751266acd32/large.jpg"} + {:name "Mission British Café", + :categories ["British" "Café"], + :phone "415-715-7004", + :id "c99899e3-439c-4444-9dc4-5598632aec8d"}] + [{:service "foursquare", + :foursquare-photo-id "2dda53d5-9b31-4f4e-b36b-e37b45e67de7", + :mayor "biggie"} + "SoMa Japanese Churros is a classic and popular place to watch the Warriors game on public holidays." + {:small + "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/small.jpg", + :medium + "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/med.jpg", + :large + "http://cloudfront.net/a0c29909-96a1-4292-95b7-0d3daaf25634/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "flare", :username "bob"} + "Haight Soul Food Hotel & Restaurant is a amazing and acceptable place to have breakfast weekday afternoons." + {:small + "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/small.jpg", + :medium + "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/med.jpg", + :large + "http://cloudfront.net/836d04ac-32b2-4c7a-888f-155ca6e93634/large.jpg"} + {:name "Haight Soul Food Hotel & Restaurant", + :categories ["Soul Food" "Hotel & Restaurant"], + :phone "415-786-9541", + :id "11a72eb3-9e96-4703-9a01-e8c2a9469046"}] + [{:service "facebook", + :facebook-photo-id "01dcf27f-6233-415d-8b8b-4fe7a841b457", + :url + "http://facebook.com/photos/01dcf27f-6233-415d-8b8b-4fe7a841b457"} + "Lower Pac Heights Cage-Free Coffee House is a atmospheric and well-decorated place to have brunch weekend mornings." + {:small + "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/small.jpg", + :medium + "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/med.jpg", + :large + "http://cloudfront.net/37488ad8-7f52-40e4-9330-505b7174701d/large.jpg"} + {:name "Lower Pac Heights Cage-Free Coffee House", + :categories ["Cage-Free" "Coffee House"], + :phone "415-697-9309", + :id "02b1f618-41a0-406b-96dd-1a017f630b81"}] + [{:service "yelp", + :yelp-photo-id "ad5aa48a-bdda-4396-86f3-1dc63b40a0d1", + :categories ["Gormet" "Pizzeria"]} + "Haight Gormet Pizzeria is a fantastic and modern place to pitch an investor during winter." + {:small + "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/small.jpg", + :medium + "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/med.jpg", + :large + "http://cloudfront.net/482b6b0e-88ab-4963-bc96-00add6dde0ec/large.jpg"} + {:name "Haight Gormet Pizzeria", + :categories ["Gormet" "Pizzeria"], + :phone "415-869-2197", + :id "0425bdd0-3f57-4108-80e3-78335327355a"}] + [{:service "facebook", + :facebook-photo-id "f62fb4d0-d254-4496-a5c9-01ed4f7a3651", + :url + "http://facebook.com/photos/f62fb4d0-d254-4496-a5c9-01ed4f7a3651"} + "Rasta's Paleo Café is a wonderful and family-friendly place to have a drink on Taco Tuesday." + {:small + "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/small.jpg", + :medium + "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/med.jpg", + :large + "http://cloudfront.net/96553ade-9bcd-464f-888e-7b4b621e44fd/large.jpg"} + {:name "Rasta's Paleo Café", + :categories ["Paleo" "Café"], + :phone "415-392-6341", + :id "4f9e69be-f06c-46a0-bb8b-f3ddd8218ca1"}] + [{:service "flare", :username "bob"} + "Kyle's Low-Carb Grill is a horrible and popular place to nurse a hangover weekend mornings." + {:small + "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/small.jpg", + :medium + "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/med.jpg", + :large + "http://cloudfront.net/048415e9-f535-44c3-85ce-cb08a29bb107/large.jpg"} + {:name "Kyle's Low-Carb Grill", + :categories ["Low-Carb" "Grill"], + :phone "415-992-8278", + :id "b27f50c6-55eb-48b0-9fee-17a6ef5243bd"}] + [{:service "foursquare", + :foursquare-photo-id "124a768c-40b4-433c-b907-4ebcb575dd51", + :mayor "biggie"} + "Pacific Heights Red White & Blue Bar & Grill is a modern and historical place to have a after-work cocktail with your pet dog." + {:small + "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/small.jpg", + :medium + "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/med.jpg", + :large + "http://cloudfront.net/7b7cbf78-f8fe-4cb1-a086-2e9cdd0fba9c/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "yelp", + :yelp-photo-id "ad2c2b45-b959-48cc-aac7-c049a3902c3a", + :categories ["American" "Churros"]} + "Sunset American Churros is a overrated and wonderful place to take a date in July." + {:small + "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/small.jpg", + :medium + "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/med.jpg", + :large + "http://cloudfront.net/18ddcbe9-b183-4281-8f43-5f318e8d841e/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "yelp", + :yelp-photo-id "1f41e84f-2cbb-4579-a7d4-43d4e8532353", + :categories ["Paleo" "Food Truck"]} + "Chinatown Paleo Food Truck is a great and modern place to have a birthday party on Thursdays." + {:small + "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/small.jpg", + :medium + "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/med.jpg", + :large + "http://cloudfront.net/2ca5c924-bb42-4f7f-a6bd-eb17b1c9e6aa/large.jpg"} + {:name "Chinatown Paleo Food Truck", + :categories ["Paleo" "Food Truck"], + :phone "415-583-4380", + :id "aa9b5ce9-db74-470e-8573-f2faca24d546"}] + [{:service "yelp", + :yelp-photo-id "b1d569ba-ab45-4965-af9d-366dd4bc44bb", + :categories ["European" "Ice Cream Truck"]} + "Market St. European Ice Cream Truck is a popular and historical place to pitch an investor on Thursdays." + {:small + "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/small.jpg", + :medium + "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/med.jpg", + :large + "http://cloudfront.net/2731d670-e7d3-4ade-a1da-d1381355276f/large.jpg"} + {:name "Market St. European Ice Cream Truck", + :categories ["European" "Ice Cream Truck"], + :phone "415-555-4197", + :id "4ed53fe4-4bd9-4fa3-8f61-374ea75129ca"}] + [{:service "twitter", + :mentions ["@haight_gormet_pizzeria"], + :tags ["#gormet" "#pizzeria"], + :username "rasta_toucan"} + "Haight Gormet Pizzeria is a well-decorated and amazing place to take visiting friends and relatives on Taco Tuesday." + {:small + "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/small.jpg", + :medium + "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/med.jpg", + :large + "http://cloudfront.net/485712f2-ec47-43df-9a32-f872aed21d81/large.jpg"} + {:name "Haight Gormet Pizzeria", + :categories ["Gormet" "Pizzeria"], + :phone "415-869-2197", + :id "0425bdd0-3f57-4108-80e3-78335327355a"}] + [{:service "flare", :username "bob"} + "SoMa TaquerÃa Diner is a well-decorated and acceptable place to have a after-work cocktail weekend evenings." + {:small + "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/small.jpg", + :medium + "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/med.jpg", + :large + "http://cloudfront.net/c9e18865-81de-4444-93fa-dd77ff19977e/large.jpg"} + {:name "SoMa TaquerÃa Diner", + :categories ["TaquerÃa" "Diner"], + :phone "415-947-9521", + :id "f97ede4a-074f-4e24-babc-5c44f2be9c36"}] + [{:service "flare", :username "lucky_pigeon"} + "Cam's Soul Food Ice Cream Truck is a acceptable and fantastic place to people-watch with your pet toucan." + {:small + "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/small.jpg", + :medium + "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/med.jpg", + :large + "http://cloudfront.net/f38cd426-8b7f-4481-96b2-cb44095a0709/large.jpg"} + {:name "Cam's Soul Food Ice Cream Truck", + :categories ["Soul Food" "Ice Cream Truck"], + :phone "415-270-8888", + :id "f474e587-1801-43ea-93d5-4c4fd96460b8"}] + [{:service "flare", :username "bob"} + "Tenderloin Cage-Free Sushi is a delicious and delicious place to have breakfast with your pet toucan." + {:small + "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/small.jpg", + :medium + "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/med.jpg", + :large + "http://cloudfront.net/45dc5a3b-a0c5-4f69-afc8-711cfc5f84b5/large.jpg"} + {:name "Tenderloin Cage-Free Sushi", + :categories ["Cage-Free" "Sushi"], + :phone "415-348-0644", + :id "0b6c036f-82b0-4008-bdfe-5360dd93fb75"}] + [{:service "foursquare", + :foursquare-photo-id "e48f5b9f-a12e-4339-b6d7-992c44aa481b", + :mayor "kyle"} + "Polk St. Korean Taqueria is a horrible and atmospheric place to people-watch in July." + {:small + "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/small.jpg", + :medium + "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/med.jpg", + :large + "http://cloudfront.net/fc6f7000-2347-49dc-aeff-22559cfd7ce8/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "flare", :username "jessica"} + "Sunset Homestyle Grill is a world-famous and modern place to sip Champagne Friday nights." + {:small + "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/small.jpg", + :medium + "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/med.jpg", + :large + "http://cloudfront.net/d3f31d2b-f10a-44cb-91a7-37e5b9491fbc/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "twitter", + :mentions ["@chinatown_american_bakery"], + :tags ["#american" "#bakery"], + :username "amy"} + "Chinatown American Bakery is a underground and underappreciated place to have brunch in June." + {:small + "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/small.jpg", + :medium + "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/med.jpg", + :large + "http://cloudfront.net/1707f0ac-6df5-4340-a5ef-c31609c4ead0/large.jpg"} + {:name "Chinatown American Bakery", + :categories ["American" "Bakery"], + :phone "415-658-7393", + :id "cf55cdbd-c614-4be1-8496-0e11b195d16f"}] + [{:service "yelp", + :yelp-photo-id "6f0c3b95-145b-4b9f-8c63-a04ca70e3835", + :categories ["Red White & Blue" "Bar & Grill"]} + "Pacific Heights Red White & Blue Bar & Grill is a swell and acceptable place to take visiting friends and relatives with your pet toucan." + {:small + "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/small.jpg", + :medium + "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/med.jpg", + :large + "http://cloudfront.net/37a5f9f6-b1bd-405f-bec2-fad8a476cf62/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "twitter", + :mentions ["@sunset_american_churros"], + :tags ["#american" "#churros"], + :username "amy"} + "Sunset American Churros is a popular and delicious place to watch the Giants game on Taco Tuesday." + {:small + "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/small.jpg", + :medium + "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/med.jpg", + :large + "http://cloudfront.net/75a631a2-43b1-47a3-a7a6-687c853b857c/large.jpg"} + {:name "Sunset American Churros", + :categories ["American" "Churros"], + :phone "415-191-5018", + :id "2e88c921-29fb-489b-a956-d3ba1182da73"}] + [{:service "foursquare", + :foursquare-photo-id "9ae208eb-c7c1-43b1-9a1e-f7a4d276e8e2", + :mayor "cam_saul"} + "Sunset Homestyle Grill is a underappreciated and swell place to watch the Giants game on Thursdays." + {:small + "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/small.jpg", + :medium + "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/med.jpg", + :large + "http://cloudfront.net/2bd0472b-267c-4be6-992d-94e72edfe719/large.jpg"} + {:name "Sunset Homestyle Grill", + :categories ["Homestyle" "Grill"], + :phone "415-356-7052", + :id "c57673cd-f2d0-4bbc-aed0-6c166d7cf2c3"}] + [{:service "facebook", + :facebook-photo-id "647413c2-f132-46e9-ac47-155cec8346d0", + :url + "http://facebook.com/photos/647413c2-f132-46e9-ac47-155cec8346d0"} + "Rasta's British Food Truck is a classic and historical place to sip a glass of expensive wine Friday nights." + {:small + "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/small.jpg", + :medium + "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/med.jpg", + :large + "http://cloudfront.net/253137fd-7a54-431e-a991-a1a5175ff9fc/large.jpg"} + {:name "Rasta's British Food Truck", + :categories ["British" "Food Truck"], + :phone "415-958-9031", + :id "b6616c97-01d0-488f-a855-bcd6efe2b899"}] + [{:service "flare", :username "jessica"} + "Polk St. Korean Taqueria is a groovy and great place to watch the Giants game on public holidays." + {:small + "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/small.jpg", + :medium + "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/med.jpg", + :large + "http://cloudfront.net/fabc9014-285d-41b4-8b1c-a66ecae0e8b0/large.jpg"} + {:name "Polk St. Korean Taqueria", + :categories ["Korean" "Taqueria"], + :phone "415-511-5531", + :id "ddb09b32-6f0b-4e54-98c7-14398f16ca4e"}] + [{:service "foursquare", + :foursquare-photo-id "2d74b28c-aab1-489e-a18b-1bfb3495cc19", + :mayor "sameer"} + "Nob Hill Gluten-Free Coffee House is a decent and acceptable place to watch the Giants game on Taco Tuesday." + {:small + "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/small.jpg", + :medium + "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/med.jpg", + :large + "http://cloudfront.net/9e1072a8-82ee-46ee-a625-578384e641f7/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "foursquare", + :foursquare-photo-id "18546192-88d1-4c74-a40e-b502d32f7a5b", + :mayor "mandy"} + "Oakland American Grill is a acceptable and popular place to conduct a business meeting on Taco Tuesday." + {:small + "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/small.jpg", + :medium + "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/med.jpg", + :large + "http://cloudfront.net/beff31d4-4685-4805-bf89-dd705ada2fe2/large.jpg"} + {:name "Oakland American Grill", + :categories ["American" "Grill"], + :phone "415-660-0889", + :id "856f907d-b669-4b9c-8337-bf9c88883746"}] + [{:service "twitter", + :mentions ["@pacific_heights_soul_food_coffee_house"], + :tags ["#soul" "#food" "#coffee" "#house"], + :username "jane"} + "Pacific Heights Soul Food Coffee House is a decent and swell place to catch a bite to eat on Saturday night." + {:small + "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/small.jpg", + :medium + "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/med.jpg", + :large + "http://cloudfront.net/2018a275-f498-47bf-a15b-e7b1c57147f0/large.jpg"} + {:name "Pacific Heights Soul Food Coffee House", + :categories ["Soul Food" "Coffee House"], + :phone "415-838-3464", + :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"}] + [{:service "foursquare", + :foursquare-photo-id "2efbeca5-5a55-4310-abdd-936ae97bc1bc", + :mayor "biggie"} + "Polk St. Deep-Dish Hotel & Restaurant is a delicious and acceptable place to take a date on Taco Tuesday." + {:small + "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/small.jpg", + :medium + "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/med.jpg", + :large + "http://cloudfront.net/1bc7f807-4ee1-4c37-af6e-918e978b95fd/large.jpg"} + {:name "Polk St. Deep-Dish Hotel & Restaurant", + :categories ["Deep-Dish" "Hotel & Restaurant"], + :phone "415-666-8681", + :id "47f1698e-ae11-46f5-818b-85a59d0affba"}] + [{:service "twitter", + :mentions ["@pacific_heights_soul_food_coffee_house"], + :tags ["#soul" "#food" "#coffee" "#house"], + :username "cam_saul"} + "Pacific Heights Soul Food Coffee House is a fantastic and popular place to watch the Giants game in July." + {:small + "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/small.jpg", + :medium + "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/med.jpg", + :large + "http://cloudfront.net/4ad94fa2-4174-4617-872b-a925006d0841/large.jpg"} + {:name "Pacific Heights Soul Food Coffee House", + :categories ["Soul Food" "Coffee House"], + :phone "415-838-3464", + :id "6aed1816-4c64-4f76-8e2a-619528e5b48d"}] + [{:service "yelp", + :yelp-photo-id "86617b70-de39-40aa-9ca2-9c33c5bd1fa4", + :categories ["Gormet" "Restaurant"]} + "Tenderloin Gormet Restaurant is a horrible and underground place to have brunch in July." + {:small + "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/small.jpg", + :medium + "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/med.jpg", + :large + "http://cloudfront.net/7120084f-5792-425f-8469-170b6fa01df1/large.jpg"} + {:name "Tenderloin Gormet Restaurant", + :categories ["Gormet" "Restaurant"], + :phone "415-127-4197", + :id "54a9eac8-d80d-4af8-b6d7-34651a60e59c"}] + [{:service "twitter", + :mentions ["@pacific_heights_red_white_&_blue_bar_&_grill"], + :tags ["#red" "#white" "#&" "#blue" "#bar" "#&" "#grill"], + :username "mandy"} + "Pacific Heights Red White & Blue Bar & Grill is a underappreciated and modern place to meet new friends with friends." + {:small + "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/small.jpg", + :medium + "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/med.jpg", + :large + "http://cloudfront.net/a0d24e43-c01d-4b02-b236-e8dc2155aa37/large.jpg"} + {:name "Pacific Heights Red White & Blue Bar & Grill", + :categories ["Red White & Blue" "Bar & Grill"], + :phone "415-208-2550", + :id "c7547aa1-94c1-44bd-bf5a-8655e4698ed8"}] + [{:service "flare", :username "jane"} + "Nob Hill Gluten-Free Coffee House is a underappreciated and swell place to take visiting friends and relatives with your pet dog." + {:small + "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/small.jpg", + :medium + "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/med.jpg", + :large + "http://cloudfront.net/1e92e043-2910-4d1b-b72e-43608cc0e2ff/large.jpg"} + {:name "Nob Hill Gluten-Free Coffee House", + :categories ["Gluten-Free" "Coffee House"], + :phone "415-605-9554", + :id "df57ff6d-1b5f-46da-b292-32321c6b1a7e"}] + [{:service "facebook", + :facebook-photo-id "83699a56-416c-49cc-a31a-dd6b4c5aa91b", + :url + "http://facebook.com/photos/83699a56-416c-49cc-a31a-dd6b4c5aa91b"} + "Mission Japanese Coffee House is a modern and historical place to take visiting friends and relatives on Taco Tuesday." + {:small + "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/small.jpg", + :medium + "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/med.jpg", + :large + "http://cloudfront.net/7dcd5015-ddf0-40db-a38e-c40308a00720/large.jpg"} + {:name "Mission Japanese Coffee House", + :categories ["Japanese" "Coffee House"], + :phone "415-561-0506", + :id "60dd274e-0cbf-4521-946d-8a4e0f151150"}] + [{:service "twitter", + :mentions ["@oakland_low_carb_bakery"], + :tags ["#low-carb" "#bakery"], + :username "bob"} + "Oakland Low-Carb Bakery is a modern and fantastic place to conduct a business meeting on a Tuesday afternoon." + {:small + "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/small.jpg", + :medium + "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/med.jpg", + :large + "http://cloudfront.net/6b16ed76-4ccb-4ce2-b255-55f69271ddf1/large.jpg"} + {:name "Oakland Low-Carb Bakery", + :categories ["Low-Carb" "Bakery"], + :phone "415-546-0101", + :id "da7dd72d-60fb-495b-a2c0-1e2ae73a1a86"}] + [{:service "yelp", + :yelp-photo-id "de90a66b-d19e-4665-b0c2-c64ba899a02b", + :categories ["Old-Fashioned" "Coffee House"]} + "Cam's Old-Fashioned Coffee House is a world-famous and decent place to sip a glass of expensive wine on a Tuesday afternoon." + {:small + "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/small.jpg", + :medium + "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/med.jpg", + :large + "http://cloudfront.net/1282e126-e15b-4aa4-a3dc-869ea9d6d6c2/large.jpg"} + {:name "Cam's Old-Fashioned Coffee House", + :categories ["Old-Fashioned" "Coffee House"], + :phone "415-868-2973", + :id "27592c2b-e682-44bb-be28-8e9a622becca"}] + [{:service "flare", :username "amy"} + "Rasta's Mexican Sushi is a fantastic and atmospheric place to have a after-work cocktail weekday afternoons." + {:small + "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/small.jpg", + :medium + "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/med.jpg", + :large + "http://cloudfront.net/65dc9189-9de4-4bdc-bdfb-e2ff8247829b/large.jpg"} + {:name "Rasta's Mexican Sushi", + :categories ["Mexican" "Sushi"], + :phone "415-387-1284", + :id "e4912a22-e6ac-4806-8377-6497bf533a21"}] + [{:service "facebook", + :facebook-photo-id "bdd68c35-bffc-4091-b32c-bf8bf3a8f834", + :url + "http://facebook.com/photos/bdd68c35-bffc-4091-b32c-bf8bf3a8f834"} + "SoMa Japanese Churros is a underappreciated and swell place to take a date Friday nights." + {:small + "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/small.jpg", + :medium + "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/med.jpg", + :large + "http://cloudfront.net/b8fe2a0a-3cfc-4e39-afb7-c1fa4ff708e2/large.jpg"} + {:name "SoMa Japanese Churros", + :categories ["Japanese" "Churros"], + :phone "415-404-1510", + :id "373858b2-e634-45d0-973d-4d0fed8c438b"}] + [{:service "flare", :username "lucky_pigeon"} + "Lucky's Cage-Free Liquor Store is a modern and groovy place to take a date with friends." + {:small + "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/small.jpg", + :medium + "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/med.jpg", + :large + "http://cloudfront.net/6bccb82b-1102-4f60-aa9b-372ee1164118/large.jpg"} + {:name "Lucky's Cage-Free Liquor Store", + :categories ["Cage-Free" "Liquor Store"], + :phone "415-341-3219", + :id "968eac8d-1fd0-443d-a7ff-b48810ab0f69"}]]]] diff --git a/test/metabase/test/data/dataset_definitions/test-data.edn b/test/metabase/test/data/dataset_definitions/test-data.edn index 877eb8c623fc4c9263224f263bf35455af50e008..822abe92336decf4a96fcaae35f5e9585c1d95b8 100644 --- a/test/metabase/test/data/dataset_definitions/test-data.edn +++ b/test/metabase/test/data/dataset_definitions/test-data.edn @@ -123,6 +123,9 @@ ["Winery"]]] ; 75 ["venues" [{:field-name "name" :base-type :type/Text} + {:field-name "category_id" + :base-type :type/Integer + :fk :categories} {:field-name "latitude" :base-type :type/Float :special-type :type/Latitude} @@ -131,1115 +134,1112 @@ :special-type :type/Longitude} {:field-name "price" :base-type :type/Integer - :special-type :type/Category} - {:field-name "category_id" - :base-type :type/Integer - :fk :categories}] - [["Red Medicine" 10.0646 -165.374 3 4] ; 1 - ["Stout Burgers & Beers" 34.0996 -118.329 2 11] ; 2 - ["The Apple Pan" 34.0406 -118.428 2 11] ; 3 - ["Wurstküche" 33.9997 -118.465 2 29] ; 4 - ["Brite Spot Family Restaurant" 34.0778 -118.261 2 20] ; 5 - ["The 101 Coffee Shop" 34.1054 -118.324 2 20] ; 6 - ["Don Day Korean Restaurant" 34.0689 -118.305 2 44] ; 7 - ["25°" 34.1015 -118.342 2 11] ; 8 - ["Krua Siri" 34.1018 -118.301 1 71] ; 9 - ["Fred 62" 34.1046 -118.292 2 20] ; 10 - ["The Gorbals" 34.0474 -118.25 2 2] ; 11 - ["The Misfit Restaurant + Bar" 34.0154 -118.497 2 2] ; 12 - ["Pellicola Pizzeria" 34.0451 -118.257 1 58] ; 13 - ["Jones Hollywood" 34.0908 -118.346 3 7] ; 14 - ["BCD Tofu House" 34.0619 -118.303 2 44] ; 15 - ["Pacific Dining Car - Santa Monica" 34.0367 -118.476 4 67] ; 16 - ["Ruen Pair Thai Restaurant" 34.1021 -118.306 2 71] ; 17 - ["The Original Pantry" 34.0464 -118.263 2 20] ; 18 - ["800 Degrees Neapolitan Pizzeria" 34.0597 -118.444 2 58] ; 19 - ["Greenblatt's Delicatessen & Fine Wine Shop" 34.0981 -118.365 2 3] ; 20 - ["PizzaHacker" 37.7441 -122.421 2 58] ; 21 - ["Gordo Taqueria" 37.7822 -122.484 1 50] ; 22 - ["Taqueria Los Coyotes" 37.765 -122.42 2 50] ; 23 - ["La Tortilla" 37.7612 -122.435 1 50] ; 24 - ["Garaje" 37.7818 -122.396 2 50] ; 25 - ["Taqueria San Francisco" 37.753 -122.408 1 50] ; 26 - ["Tout Sweet Patisserie" 37.7873 -122.407 2 13] ; 27 - ["Liguria Bakery" 37.8014 -122.409 1 6] ; 28 - ["20th Century Cafe" 37.775 -122.423 2 12] ; 29 - ["Noe Valley Bakery" 37.7513 -122.434 2 6] ; 30 - ["Bludso's BBQ" 33.8894 -118.207 2 5] ; 31 - ["Boneyard Bistro" 34.1477 -118.428 3 5] ; 32 - ["My Brother's Bar-B-Q" 34.167 -118.595 2 5] ; 33 - ["Beachwood BBQ & Brewing" 33.7701 -118.191 2 10] ; 34 - ["Smoke City Market" 34.1661 -118.448 1 5] ; 35 - ["Handy Market" 34.1716 -118.335 2 3] ; 36 - ["bigmista's barbecue" 34.118 -118.26 2 5] ; 37 - ["Zeke's Smokehouse" 34.2053 -118.226 2 5] ; 38 - ["Baby Blues BBQ" 34.0003 -118.465 2 5] ; 39 - ["Dear Mom" 37.7655 -122.413 2 46] ; 40 - ["Cheese Steak Shop" 37.7855 -122.44 1 18] ; 41 - ["Little Star Pizza" 37.7665 -122.422 2 58] ; 42 - ["Marnee Thai" 37.7634 -122.482 2 71] ; 43 - ["In-N-Out Burger" 37.8078 -122.418 1 11] ; 44 - ["Tu Lan Restaurant" 37.7821 -122.41 1 4] ; 45 - ["Shanghai Dumpling King" 37.7317 -122.451 2 19] ; 46 - ["Marlowe" 37.7767 -122.396 3 2] ; 47 - ["The Residence" 37.7677 -122.429 2 7] ; 48 - ["Hotel Biron" 37.7735 -122.422 3 74] ; 49 - ["Two Sisters Bar & Books" 37.7765 -122.426 2 48] ; 50 - ["Empress of China" 37.7949 -122.406 3 15] ; 51 - ["Cole's" 34.0448 -118.25 2 7] ; 52 - ["Tam O'Shanter" 34.1254 -118.264 3 18] ; 53 - ["Yamashiro Hollywood" 34.1057 -118.342 3 2] ; 54 - ["Dal Rae Restaurant" 33.983 -118.096 4 67] ; 55 - ["Philippe the Original" 34.0597 -118.237 1 18] ; 56 - ["Musso & Frank Grill" 34.1018 -118.335 3 2] ; 57 - ["Taylor's Prime Steak House" 34.0579 -118.302 3 67] ; 58 - ["Pacific Dining Car" 34.0555 -118.266 3 2] ; 59 - ["Polo Lounge" 34.0815 -118.414 3 48] ; 60 - ["Lawry's The Prime Rib" 34.0677 -118.376 4 67] ; 61 - ["Hot Sauce and Panko" 37.7825 -122.476 1 64] ; 62 - ["Giordano Bros." 37.765 -122.422 1 18] ; 63 - ["Festa" 37.7852 -122.432 2 43] ; 64 - ["Slate" 37.765 -122.418 2 48] ; 65 - ["Playground" 37.7858 -122.43 2 43] ; 66 - ["Mint Karaoke Lounge" 37.7702 -122.426 2 43] ; 67 - ["Dimples" 37.7856 -122.43 1 43] ; 68 - ["The Virgil" 34.091 -118.287 2 48] ; 69 - ["The Drawing Room" 34.1037 -118.287 1 7] ; 70 - ["Frolic Room" 34.1016 -118.326 1 7] ; 71 - ["The Daily Pint" 34.0211 -118.466 2 7] ; 72 - ["Pineapple Hill Saloon & Grill" 34.1525 -118.448 2 7] ; 73 - ["Chez Jay" 34.0104 -118.493 2 2] ; 74 - ["Geido" 40.6778 -73.9729 2 40] ; 75 - ["Beyond Sushi" 40.7328 -73.9861 2 40] ; 76 - ["Sushi Nakazawa" 40.7318 -74.0045 4 40] ; 77 - ["Soto" 40.7321 -74.0006 2 40] ; 78 - ["Sushi Yasuda" 40.7514 -73.9736 4 40] ; 79 - ["Blue Ribbon Sushi" 40.7262 -74.0026 3 40] ; 80 - ["Tanoshi Sushi & Sake Bar" 40.7677 -73.9533 4 40] ; 81 - ["Bozu" 40.7129 -73.9576 2 40] ; 82 - ["Sushi Yasaka" 40.7794 -73.9835 2 40] ; 83 - ["Spitz Eagle Rock" 34.1411 -118.221 2 49] ; 84 - ["Cha Cha Chicken" 34.0071 -118.49 1 14] ; 85 - ["Yuca's Taqueria" 34.1092 -118.287 1 50] ; 86 - ["The Gumbo Pot" 34.072 -118.361 2 64] ; 87 - ["Kinaree Thai Bistro" 34.094 -118.344 1 71] ; 88 - ["Tacos Villa Corona" 34.1177 -118.261 1 50] ; 89 - ["Señor Fish" 34.0489 -118.238 2 50] ; 90 - ["Manuel's Original El Tepeyac Cafe" 34.0478 -118.198 2 50] ; 91 - ["Tito's Tacos" 34.0082 -118.415 1 50] ; 92 - ["33 Taps" 34.1018 -118.326 2 7] ; 93 - ["Ye Rustic Inn" 34.1044 -118.288 1 7] ; 94 - ["Rush Street" 34.023 -118.395 2 2] ; 95 - ["Busby's West" 34.0372 -118.469 2 48] ; 96 - ["Barney's Beanery" 34.0908 -118.375 2 46] ; 97 - ["Lucky Baldwin's Pub" 34.1454 -118.149 2 7] ; 98 - ["Golden Road Brewing" 34.1505 -118.274 2 10] ; 99 - ["Mohawk Bend" 34.0777 -118.265 2 46]]] ; 100 - ["checkins" [{:field-name "user_id" + :special-type :type/Category}] + [["Red Medicine" 4 10.0646 -165.374 3] ; 1 + ["Stout Burgers & Beers" 11 34.0996 -118.329 2] ; 2 + ["The Apple Pan" 11 34.0406 -118.428 2] ; 3 + ["Wurstküche" 29 33.9997 -118.465 2] ; 4 + ["Brite Spot Family Restaurant" 20 34.0778 -118.261 2] ; 5 + ["The 101 Coffee Shop" 20 34.1054 -118.324 2] ; 6 + ["Don Day Korean Restaurant" 44 34.0689 -118.305 2] ; 7 + ["25°" 11 34.1015 -118.342 2] ; 8 + ["Krua Siri" 71 34.1018 -118.301 1] ; 9 + ["Fred 62" 20 34.1046 -118.292 2] ; 10 + ["The Gorbals" 2 34.0474 -118.25 2] ; 11 + ["The Misfit Restaurant + Bar" 2 34.0154 -118.497 2] ; 12 + ["Pellicola Pizzeria" 58 34.0451 -118.257 1] ; 13 + ["Jones Hollywood" 7 34.0908 -118.346 3] ; 14 + ["BCD Tofu House" 44 34.0619 -118.303 2] ; 15 + ["Pacific Dining Car - Santa Monica" 67 34.0367 -118.476 4] ; 16 + ["Ruen Pair Thai Restaurant" 71 34.1021 -118.306 2] ; 17 + ["The Original Pantry" 20 34.0464 -118.263 2] ; 18 + ["800 Degrees Neapolitan Pizzeria" 58 34.0597 -118.444 2] ; 19 + ["Greenblatt's Delicatessen & Fine Wine Shop" 3 34.0981 -118.365 2] ; 20 + ["PizzaHacker" 58 37.7441 -122.421 2] ; 21 + ["Gordo Taqueria" 50 37.7822 -122.484 1] ; 22 + ["Taqueria Los Coyotes" 50 37.765 -122.42 2] ; 23 + ["La Tortilla" 50 37.7612 -122.435 1] ; 24 + ["Garaje" 50 37.7818 -122.396 2] ; 25 + ["Taqueria San Francisco" 50 37.753 -122.408 1] ; 26 + ["Tout Sweet Patisserie" 13 37.7873 -122.407 2] ; 27 + ["Liguria Bakery" 6 37.8014 -122.409 1] ; 28 + ["20th Century Cafe" 12 37.775 -122.423 2] ; 29 + ["Noe Valley Bakery" 6 37.7513 -122.434 2] ; 30 + ["Bludso's BBQ" 5 33.8894 -118.207 2] ; 31 + ["Boneyard Bistro" 5 34.1477 -118.428 3] ; 32 + ["My Brother's Bar-B-Q" 5 34.167 -118.595 2] ; 33 + ["Beachwood BBQ & Brewing" 10 33.7701 -118.191 2] ; 34 + ["Smoke City Market" 5 34.1661 -118.448 1] ; 35 + ["Handy Market" 3 34.1716 -118.335 2] ; 36 + ["bigmista's barbecue" 5 34.118 -118.26 2] ; 37 + ["Zeke's Smokehouse" 5 34.2053 -118.226 2] ; 38 + ["Baby Blues BBQ" 5 34.0003 -118.465 2] ; 39 + ["Dear Mom" 46 37.7655 -122.413 2] ; 40 + ["Cheese Steak Shop" 18 37.7855 -122.44 1] ; 41 + ["Little Star Pizza" 58 37.7665 -122.422 2] ; 42 + ["Marnee Thai" 71 37.7634 -122.482 2] ; 43 + ["In-N-Out Burger" 11 37.8078 -122.418 1] ; 44 + ["Tu Lan Restaurant" 4 37.7821 -122.41 1] ; 45 + ["Shanghai Dumpling King" 19 37.7317 -122.451 2] ; 46 + ["Marlowe" 2 37.7767 -122.396 3] ; 47 + ["The Residence" 7 37.7677 -122.429 2] ; 48 + ["Hotel Biron" 74 37.7735 -122.422 3] ; 49 + ["Two Sisters Bar & Books" 48 37.7765 -122.426 2] ; 50 + ["Empress of China" 15 37.7949 -122.406 3] ; 51 + ["Cole's" 7 34.0448 -118.25 2] ; 52 + ["Tam O'Shanter" 18 34.1254 -118.264 3] ; 53 + ["Yamashiro Hollywood" 2 34.1057 -118.342 3] ; 54 + ["Dal Rae Restaurant" 67 33.983 -118.096 4] ; 55 + ["Philippe the Original" 18 34.0597 -118.237 1] ; 56 + ["Musso & Frank Grill" 2 34.1018 -118.335 3] ; 57 + ["Taylor's Prime Steak House" 67 34.0579 -118.302 3] ; 58 + ["Pacific Dining Car" 2 34.0555 -118.266 3] ; 59 + ["Polo Lounge" 48 34.0815 -118.414 3] ; 60 + ["Lawry's The Prime Rib" 67 34.0677 -118.376 4] ; 61 + ["Hot Sauce and Panko" 64 37.7825 -122.476 1] ; 62 + ["Giordano Bros." 18 37.765 -122.422 1] ; 63 + ["Festa" 43 37.7852 -122.432 2] ; 64 + ["Slate" 48 37.765 -122.418 2] ; 65 + ["Playground" 43 37.7858 -122.43 2] ; 66 + ["Mint Karaoke Lounge" 43 37.7702 -122.426 2] ; 67 + ["Dimples" 43 37.7856 -122.43 1] ; 68 + ["The Virgil" 48 34.091 -118.287 2] ; 69 + ["The Drawing Room" 7 34.1037 -118.287 1] ; 70 + ["Frolic Room" 7 34.1016 -118.326 1] ; 71 + ["The Daily Pint" 7 34.0211 -118.466 2] ; 72 + ["Pineapple Hill Saloon & Grill" 7 34.1525 -118.448 2] ; 73 + ["Chez Jay" 2 34.0104 -118.493 2] ; 74 + ["Geido" 40 40.6778 -73.9729 2] ; 75 + ["Beyond Sushi" 40 40.7328 -73.9861 2] ; 76 + ["Sushi Nakazawa" 40 40.7318 -74.0045 4] ; 77 + ["Soto" 40 40.7321 -74.0006 2] ; 78 + ["Sushi Yasuda" 40 40.7514 -73.9736 4] ; 79 + ["Blue Ribbon Sushi" 40 40.7262 -74.0026 3] ; 80 + ["Tanoshi Sushi & Sake Bar" 40 40.7677 -73.9533 4] ; 81 + ["Bozu" 40 40.7129 -73.9576 2] ; 82 + ["Sushi Yasaka" 40 40.7794 -73.9835 2] ; 83 + ["Spitz Eagle Rock" 49 34.1411 -118.221 2] ; 84 + ["Cha Cha Chicken" 14 34.0071 -118.49 1] ; 85 + ["Yuca's Taqueria" 50 34.1092 -118.287 1] ; 86 + ["The Gumbo Pot" 64 34.072 -118.361 2] ; 87 + ["Kinaree Thai Bistro" 71 34.094 -118.344 1] ; 88 + ["Tacos Villa Corona" 50 34.1177 -118.261 1] ; 89 + ["Señor Fish" 50 34.0489 -118.238 2] ; 90 + ["Manuel's Original El Tepeyac Cafe" 50 34.0478 -118.198 2] ; 91 + ["Tito's Tacos" 50 34.0082 -118.415 1] ; 92 + ["33 Taps" 7 34.1018 -118.326 2] ; 93 + ["Ye Rustic Inn" 7 34.1044 -118.288 1] ; 94 + ["Rush Street" 2 34.023 -118.395 2] ; 95 + ["Busby's West" 48 34.0372 -118.469 2] ; 96 + ["Barney's Beanery" 46 34.0908 -118.375 2] ; 97 + ["Lucky Baldwin's Pub" 7 34.1454 -118.149 2] ; 98 + ["Golden Road Brewing" 10 34.1505 -118.274 2] ; 99 + ["Mohawk Bend" 46 34.0777 -118.265 2]]] ; 100 + ["checkins" [{:field-name "date" + :base-type :type/Date} + {:field-name "user_id" :base-type :type/Integer :fk :users} {:field-name "venue_id" :base-type :type/Integer - :fk :venues} - {:field-name "date" - :base-type :type/Date}] - [[5 12 #t "2014-04-07"] - [1 31 #t "2014-09-18"] - [8 56 #t "2014-09-15"] - [5 4 #t "2014-03-11"] - [3 49 #t "2013-05-05"] - [3 35 #t "2015-07-04"] - [12 6 #t "2014-04-11"] - [11 16 #t "2014-05-13"] - [3 79 #t "2014-05-26"] - [3 55 #t "2015-08-22"] - [15 23 #t "2013-03-25"] - [5 25 #t "2014-11-16"] - [6 80 #t "2014-05-17"] - [9 93 #t "2015-09-07"] - [5 61 #t "2015-02-19"] - [11 34 #t "2015-02-19"] - [8 69 #t "2014-08-31"] - [3 27 #t "2015-08-05"] - [11 70 #t "2014-07-31"] - [11 81 #t "2014-09-14"] - [4 73 #t "2015-12-10"] - [8 18 #t "2015-02-17"] - [8 71 #t "2014-04-02"] - [12 45 #t "2014-04-04"] - [12 31 #t "2014-07-05"] - [8 67 #t "2014-05-17"] - [11 57 #t "2015-02-15"] - [10 100 #t "2015-05-02"] - [15 7 #t "2014-09-27"] - [5 48 #t "2014-07-20"] - [8 27 #t "2013-05-12"] - [14 31 #t "2014-02-17"] - [5 6 #t "2015-10-07"] - [14 28 #t "2014-09-26"] - [10 56 #t "2014-07-19"] - [8 19 #t "2015-09-29"] - [4 48 #t "2015-11-19"] - [10 11 #t "2015-11-15"] - [11 65 #t "2015-04-30"] - [6 6 #t "2014-09-14"] - [10 62 #t "2013-07-05"] - [11 88 #t "2015-03-05"] - [5 51 #t "2015-11-30"] - [2 61 #t "2013-11-19"] - [9 59 #t "2013-08-24"] - [9 66 #t "2015-06-26"] - [14 67 #t "2014-07-19"] - [12 15 #t "2015-07-18"] - [5 12 #t "2015-04-07"] - [8 13 #t "2013-08-02"] - [13 30 #t "2014-06-28"] - [4 8 #t "2014-10-13"] - [6 65 #t "2014-05-05"] - [4 93 #t "2015-05-08"] - [15 14 #t "2014-12-22"] - [3 99 #t "2014-07-24"] - [2 15 #t "2015-03-06"] - [4 40 #t "2015-11-09"] - [13 24 #t "2014-06-11"] - [10 69 #t "2014-03-24"] - [3 15 #t "2013-11-01"] - [10 3 #t "2013-05-28"] - [5 15 #t "2015-12-24"] - [6 34 #t "2014-08-18"] - [7 29 #t "2014-04-13"] - [6 86 #t "2015-02-21"] - [3 28 #t "2014-05-25"] - [5 29 #t "2014-09-16"] - [8 85 #t "2014-03-14"] - [11 65 #t "2014-11-20"] - [14 93 #t "2014-01-07"] - [1 1 #t "2015-04-18"] - [11 75 #t "2013-08-07"] - [11 91 #t "2015-11-14"] - [7 97 #t "2015-09-11"] - [9 9 #t "2014-03-28"] - [14 33 #t "2014-03-03"] - [4 3 #t "2015-03-02"] - [8 19 #t "2014-05-07"] - [1 99 #t "2013-12-27"] - [7 18 #t "2013-07-23"] - [13 30 #t "2014-06-28"] - [11 17 #t "2015-02-19"] - [12 58 #t "2015-12-02"] - [13 82 #t "2014-11-15"] - [8 69 #t "2015-07-01"] - [14 95 #t "2014-08-18"] - [6 75 #t "2015-05-29"] - [14 83 #t "2013-10-29"] - [7 66 #t "2014-02-10"] - [11 73 #t "2015-04-09"] - [4 93 #t "2014-08-02"] - [2 18 #t "2013-07-02"] - [10 17 #t "2015-02-09"] - [8 60 #t "2013-10-27"] - [13 24 #t "2014-02-11"] - [2 71 #t "2014-11-25"] - [11 29 #t "2014-01-04"] - [13 91 #t "2015-11-27"] - [9 21 #t "2014-07-26"] - [11 99 #t "2015-09-04"] - [5 44 #t "2013-12-02"] - [8 51 #t "2015-04-10"] - [8 9 #t "2015-09-26"] - [11 19 #t "2014-12-01"] - [2 38 #t "2014-02-10"] - [10 12 #t "2014-07-14"] - [10 30 #t "2013-12-15"] - [8 33 #t "2013-03-16"] - [9 49 #t "2015-09-17"] - [6 38 #t "2013-05-19"] - [15 93 #t "2014-03-12"] - [2 36 #t "2013-01-27"] - [7 98 #t "2015-09-17"] - [4 6 #t "2015-09-18"] - [6 34 #t "2013-09-16"] - [11 73 #t "2014-02-20"] - [14 46 #t "2014-07-05"] - [1 44 #t "2014-10-18"] - [10 83 #t "2013-12-22"] - [3 21 #t "2014-11-05"] - [12 57 #t "2014-12-19"] - [10 77 #t "2015-07-25"] - [10 97 #t "2013-08-05"] - [14 8 #t "2015-04-16"] - [12 13 #t "2015-09-11"] - [15 81 #t "2015-10-29"] - [13 17 #t "2014-08-18"] - [15 2 #t "2014-08-25"] - [8 74 #t "2014-08-11"] - [7 90 #t "2013-02-13"] - [4 84 #t "2014-08-26"] - [10 87 #t "2014-01-09"] - [8 88 #t "2013-08-21"] - [6 85 #t "2015-12-26"] - [8 62 #t "2014-05-21"] - [4 97 #t "2015-02-15"] - [4 65 #t "2014-02-11"] - [9 1 #t "2015-10-08"] - [13 96 #t "2014-10-10"] - [10 83 #t "2015-11-22"] - [15 24 #t "2013-07-24"] - [10 38 #t "2014-09-14"] - [12 3 #t "2015-11-13"] - [4 78 #t "2014-10-13"] - [14 12 #t "2014-07-17"] - [3 18 #t "2014-05-31"] - [11 10 #t "2013-10-19"] - [11 81 #t "2013-03-13"] - [5 61 #t "2014-11-21"] - [13 81 #t "2014-12-17"] - [13 24 #t "2014-09-04"] - [8 54 #t "2013-02-21"] - [10 42 #t "2014-07-08"] - [7 97 #t "2014-09-23"] - [3 14 #t "2013-03-08"] - [12 58 #t "2014-01-31"] - [11 100 #t "2015-09-04"] - [8 90 #t "2014-03-06"] - [12 20 #t "2015-05-05"] - [3 47 #t "2014-06-30"] - [5 44 #t "2015-01-04"] - [1 47 #t "2013-09-10"] - [7 11 #t "2015-05-22"] - [15 87 #t "2013-12-29"] - [2 66 #t "2014-07-17"] - [2 52 #t "2014-07-09"] - [11 3 #t "2015-06-29"] - [9 12 #t "2013-09-16"] - [2 39 #t "2014-07-17"] - [11 3 #t "2014-03-13"] - [10 47 #t "2015-03-04"] - [15 78 #t "2015-08-21"] - [12 3 #t "2014-04-19"] - [11 45 #t "2014-11-07"] - [2 75 #t "2013-05-03"] - [10 9 #t "2013-03-08"] - [2 51 #t "2013-10-04"] - [4 4 #t "2014-07-10"] - [8 57 #t "2014-07-16"] - [8 38 #t "2014-08-15"] - [5 31 #t "2014-01-06"] - [3 54 #t "2015-05-27"] - [10 84 #t "2015-07-05"] - [5 54 #t "2013-03-01"] - [3 43 #t "2015-08-06"] - [8 70 #t "2014-11-09"] - [2 59 #t "2014-07-30"] - [11 18 #t "2015-01-14"] - [9 75 #t "2013-06-30"] - [13 41 #t "2015-08-01"] - [7 87 #t "2014-02-06"] - [6 91 #t "2013-03-19"] - [7 90 #t "2013-06-23"] - [8 61 #t "2014-04-11"] - [5 35 #t "2014-10-28"] - [2 82 #t "2014-06-09"] - [9 35 #t "2013-10-23"] - [6 52 #t "2014-09-28"] - [6 96 #t "2015-09-04"] - [11 59 #t "2015-09-18"] - [12 34 #t "2015-08-09"] - [10 80 #t "2015-04-08"] - [4 78 #t "2015-03-01"] - [6 4 #t "2015-09-01"] - [4 35 #t "2014-07-07"] - [13 50 #t "2013-06-23"] - [11 69 #t "2014-03-17"] - [10 62 #t "2013-03-15"] - [13 31 #t "2015-03-27"] - [13 42 #t "2014-10-02"] - [14 86 #t "2013-05-14"] - [4 83 #t "2014-08-13"] - [9 21 #t "2015-04-18"] - [14 90 #t "2014-06-15"] - [12 65 #t "2015-05-05"] - [7 57 #t "2013-03-08"] - [12 70 #t "2014-09-06"] - [11 16 #t "2014-02-09"] - [7 38 #t "2013-10-12"] - [2 40 #t "2014-03-09"] - [8 52 #t "2015-06-22"] - [7 84 #t "2013-01-22"] - [9 4 #t "2014-08-20"] - [2 4 #t "2014-03-04"] - [8 56 #t "2014-02-03"] - [6 23 #t "2013-10-29"] - [7 87 #t "2013-10-02"] - [5 28 #t "2014-02-14"] - [5 19 #t "2013-09-08"] - [2 13 #t "2014-06-30"] - [12 65 #t "2013-07-25"] - [4 73 #t "2015-11-06"] - [7 56 #t "2013-07-14"] - [1 46 #t "2014-03-09"] - [13 58 #t "2013-07-10"] - [4 68 #t "2013-04-12"] - [14 86 #t "2014-03-09"] - [7 89 #t "2014-11-22"] - [4 42 #t "2014-04-13"] - [13 83 #t "2014-10-19"] - [10 66 #t "2014-07-07"] - [11 69 #t "2013-08-19"] - [2 18 #t "2014-11-28"] - [12 7 #t "2015-08-16"] - [7 45 #t "2014-03-20"] - [8 85 #t "2014-10-09"] - [13 27 #t "2014-05-16"] - [8 6 #t "2014-08-24"] - [9 52 #t "2013-04-11"] - [2 75 #t "2015-02-26"] - [11 65 #t "2014-05-29"] - [7 30 #t "2013-06-03"] - [11 14 #t "2013-06-26"] - [11 61 #t "2014-11-09"] - [8 81 #t "2013-06-27"] - [10 92 #t "2014-05-07"] - [3 52 #t "2014-01-26"] - [5 56 #t "2014-11-14"] - [11 75 #t "2014-04-02"] - [9 13 #t "2014-10-22"] - [4 25 #t "2015-05-18"] - [10 70 #t "2015-05-04"] - [2 48 #t "2014-06-21"] - [6 78 #t "2014-03-28"] - [12 68 #t "2014-10-24"] - [10 8 #t "2014-02-26"] - [5 63 #t "2015-10-12"] - [14 66 #t "2015-08-06"] - [2 3 #t "2014-05-14"] - [3 36 #t "2014-04-27"] - [11 71 #t "2015-04-24"] - [1 85 #t "2015-03-02"] - [13 68 #t "2015-06-22"] - [5 34 #t "2014-09-25"] - [2 75 #t "2014-07-29"] - [7 13 #t "2014-10-03"] - [12 86 #t "2014-01-10"] - [13 100 #t "2015-07-17"] - [8 59 #t "2014-10-15"] - [15 27 #t "2014-05-17"] - [13 83 #t "2013-10-11"] - [2 68 #t "2014-06-03"] - [10 23 #t "2013-04-08"] - [12 17 #t "2013-01-10"] - [8 89 #t "2014-05-01"] - [4 11 #t "2013-06-12"] - [3 97 #t "2015-05-22"] - [14 77 #t "2013-10-19"] - [10 69 #t "2014-10-10"] - [13 79 #t "2014-11-11"] - [5 95 #t "2014-07-22"] - [14 33 #t "2015-08-26"] - [2 75 #t "2014-07-10"] - [7 97 #t "2014-03-01"] - [6 88 #t "2014-08-04"] - [12 73 #t "2013-11-16"] - [14 61 #t "2013-05-21"] - [9 43 #t "2015-03-07"] - [4 44 #t "2013-08-20"] - [15 61 #t "2015-07-18"] - [11 98 #t "2014-09-29"] - [11 32 #t "2013-08-02"] - [3 94 #t "2014-12-06"] - [4 48 #t "2015-08-20"] - [11 59 #t "2014-07-21"] - [9 77 #t "2014-06-05"] - [7 10 #t "2015-04-23"] - [7 17 #t "2013-04-01"] - [9 45 #t "2015-02-13"] - [3 2 #t "2014-12-23"] - [9 85 #t "2014-12-11"] - [6 22 #t "2015-04-24"] - [12 96 #t "2013-06-11"] - [1 78 #t "2014-06-09"] - [13 29 #t "2014-02-10"] - [3 54 #t "2013-01-19"] - [11 60 #t "2014-08-30"] - [2 16 #t "2013-11-27"] - [9 41 #t "2014-05-14"] - [10 98 #t "2014-01-26"] - [13 98 #t "2015-07-01"] - [12 4 #t "2015-10-04"] - [1 63 #t "2014-03-14"] - [11 2 #t "2015-10-23"] - [14 64 #t "2014-05-27"] - [6 42 #t "2014-05-01"] - [2 44 #t "2014-09-26"] - [5 42 #t "2013-08-15"] - [5 39 #t "2013-04-26"] - [11 94 #t "2014-07-14"] - [4 17 #t "2015-08-18"] - [9 3 #t "2014-02-19"] - [3 75 #t "2014-05-18"] - [10 38 #t "2015-09-22"] - [10 74 #t "2013-03-28"] - [11 25 #t "2014-01-04"] - [7 20 #t "2014-09-14"] - [7 33 #t "2014-05-18"] - [2 40 #t "2013-02-19"] - [9 62 #t "2013-06-16"] - [6 5 #t "2014-11-25"] - [14 13 #t "2014-07-19"] - [4 82 #t "2013-04-24"] - [6 86 #t "2014-04-10"] - [15 66 #t "2013-05-31"] - [10 63 #t "2014-09-03"] - [13 46 #t "2014-06-25"] - [13 44 #t "2015-02-24"] - [5 82 #t "2014-06-19"] - [12 57 #t "2014-09-12"] - [5 96 #t "2015-03-16"] - [12 36 #t "2015-05-11"] - [6 100 #t "2015-11-28"] - [9 44 #t "2014-10-24"] - [13 70 #t "2014-04-03"] - [10 77 #t "2014-04-12"] - [13 42 #t "2015-07-23"] - [6 99 #t "2013-06-02"] - [9 22 #t "2015-07-06"] - [13 83 #t "2014-12-16"] - [13 27 #t "2014-07-20"] - [10 94 #t "2015-11-05"] - [13 70 #t "2015-01-23"] - [5 59 #t "2014-05-20"] - [12 61 #t "2013-01-25"] - [1 5 #t "2015-07-23"] - [5 95 #t "2013-08-19"] - [6 88 #t "2014-07-25"] - [3 54 #t "2013-11-07"] - [11 57 #t "2015-07-20"] - [7 27 #t "2014-08-26"] - [2 62 #t "2015-05-03"] - [4 36 #t "2014-04-14"] - [12 1 #t "2013-11-09"] - [4 27 #t "2013-07-14"] - [10 63 #t "2013-11-15"] - [6 31 #t "2014-05-22"] - [2 90 #t "2015-10-04"] - [8 2 #t "2013-12-03"] - [1 86 #t "2015-09-23"] - [7 46 #t "2014-09-05"] - [9 8 #t "2015-07-29"] - [1 51 #t "2014-05-30"] - [7 68 #t "2014-03-25"] - [14 74 #t "2015-02-23"] - [8 63 #t "2015-05-12"] - [1 68 #t "2013-04-11"] - [7 20 #t "2015-03-28"] - [14 28 #t "2014-07-24"] - [13 8 #t "2014-02-03"] - [3 40 #t "2013-09-27"] - [9 72 #t "2014-01-19"] - [11 15 #t "2013-05-14"] - [9 11 #t "2014-03-30"] - [4 10 #t "2014-12-03"] - [4 79 #t "2014-11-07"] - [4 74 #t "2013-05-09"] - [5 78 #t "2015-12-08"] - [12 74 #t "2015-06-04"] - [6 89 #t "2014-06-14"] - [2 87 #t "2013-01-23"] - [15 84 #t "2015-07-18"] - [4 65 #t "2015-03-11"] - [7 66 #t "2013-07-25"] - [10 14 #t "2013-11-29"] - [5 77 #t "2014-06-02"] - [8 74 #t "2013-04-30"] - [14 7 #t "2014-03-05"] - [4 45 #t "2013-11-05"] - [15 96 #t "2013-11-26"] - [4 45 #t "2015-01-15"] - [15 52 #t "2015-05-01"] - [6 46 #t "2014-02-25"] - [12 42 #t "2014-11-10"] - [13 17 #t "2014-05-20"] - [6 44 #t "2015-03-27"] - [3 71 #t "2014-04-14"] - [2 35 #t "2013-10-01"] - [9 74 #t "2015-03-03"] - [4 68 #t "2014-12-01"] - [6 40 #t "2013-11-25"] - [4 63 #t "2014-06-14"] - [11 12 #t "2013-08-05"] - [13 41 #t "2015-02-03"] - [11 13 #t "2014-10-05"] - [10 18 #t "2015-03-20"] - [5 20 #t "2014-05-11"] - [5 79 #t "2014-02-22"] - [7 15 #t "2013-04-15"] - [6 25 #t "2014-03-28"] - [14 9 #t "2014-08-12"] - [8 53 #t "2014-04-24"] - [9 78 #t "2014-07-02"] - [3 4 #t "2014-06-26"] - [7 3 #t "2015-10-29"] - [6 56 #t "2015-10-28"] - [4 65 #t "2014-08-12"] - [15 35 #t "2014-07-28"] - [8 49 #t "2014-09-01"] - [11 80 #t "2014-07-31"] - [10 51 #t "2015-03-01"] - [14 70 #t "2013-07-15"] - [12 18 #t "2013-10-06"] - [8 80 #t "2013-10-31"] - [15 91 #t "2013-11-16"] - [9 78 #t "2014-09-06"] - [9 88 #t "2013-06-04"] - [12 88 #t "2014-05-29"] - [7 22 #t "2013-06-07"] - [2 38 #t "2014-06-21"] - [4 7 #t "2014-05-07"] - [2 49 #t "2013-04-18"] - [13 56 #t "2014-07-19"] - [9 66 #t "2013-06-14"] - [9 57 #t "2014-07-29"] - [5 91 #t "2014-04-04"] - [10 46 #t "2015-06-08"] - [10 97 #t "2014-08-01"] - [2 53 #t "2014-07-04"] - [14 54 #t "2015-07-27"] - [2 81 #t "2013-08-17"] - [11 77 #t "2015-07-12"] - [13 39 #t "2013-08-03"] - [7 86 #t "2014-01-16"] - [14 68 #t "2014-05-07"] - [13 61 #t "2014-05-29"] - [6 90 #t "2015-09-16"] - [11 59 #t "2014-10-13"] - [11 41 #t "2015-11-07"] - [12 2 #t "2015-04-23"] - [10 76 #t "2013-10-18"] - [14 77 #t "2014-02-04"] - [2 80 #t "2014-01-04"] - [3 65 #t "2015-08-15"] - [9 59 #t "2013-04-03"] - [11 6 #t "2015-11-10"] - [9 29 #t "2013-06-30"] - [10 37 #t "2014-06-27"] - [2 26 #t "2013-12-11"] - [3 28 #t "2013-03-05"] - [2 94 #t "2015-03-13"] - [11 72 #t "2015-10-05"] - [7 39 #t "2014-08-15"] - [9 6 #t "2014-05-18"] - [10 98 #t "2013-04-26"] - [9 92 #t "2014-02-18"] - [13 39 #t "2014-08-09"] - [9 21 #t "2014-08-12"] - [2 60 #t "2014-01-27"] - [6 58 #t "2013-07-22"] - [8 41 #t "2014-05-16"] - [6 15 #t "2014-08-30"] - [12 7 #t "2015-04-21"] - [7 14 #t "2014-09-01"] - [10 43 #t "2014-11-27"] - [14 22 #t "2014-05-22"] - [2 48 #t "2015-11-22"] - [9 92 #t "2014-02-12"] - [8 48 #t "2015-10-26"] - [13 97 #t "2015-05-09"] - [6 12 #t "2014-07-14"] - [11 22 #t "2013-06-17"] - [4 23 #t "2013-03-23"] - [10 68 #t "2014-06-22"] - [5 78 #t "2014-07-16"] - [4 32 #t "2015-03-01"] - [10 33 #t "2014-05-23"] - [10 6 #t "2013-07-07"] - [7 98 #t "2015-04-04"] - [14 29 #t "2015-02-03"] - [2 53 #t "2014-09-08"] - [9 43 #t "2014-07-30"] - [14 74 #t "2015-11-01"] - [3 94 #t "2014-09-27"] - [11 86 #t "2015-09-27"] - [1 50 #t "2014-11-07"] - [4 43 #t "2013-06-19"] - [2 85 #t "2015-07-28"] - [5 24 #t "2014-11-09"] - [14 88 #t "2014-08-26"] - [6 61 #t "2014-08-09"] - [2 83 #t "2015-12-19"] - [1 38 #t "2015-07-25"] - [6 49 #t "2015-01-25"] - [12 31 #t "2015-02-09"] - [6 61 #t "2014-11-28"] - [5 50 #t "2013-06-12"] - [7 81 #t "2014-11-03"] - [9 48 #t "2014-03-27"] - [6 72 #t "2014-09-24"] - [4 59 #t "2013-10-06"] - [8 48 #t "2014-04-18"] - [11 88 #t "2015-04-10"] - [10 67 #t "2014-02-28"] - [2 74 #t "2014-01-18"] - [10 70 #t "2014-12-07"] - [4 53 #t "2014-11-07"] - [8 81 #t "2015-02-18"] - [3 72 #t "2014-05-05"] - [15 72 #t "2014-06-17"] - [4 8 #t "2015-06-13"] - [8 73 #t "2014-11-30"] - [8 93 #t "2014-09-20"] - [14 44 #t "2014-01-21"] - [8 68 #t "2014-06-05"] - [5 94 #t "2013-05-20"] - [3 7 #t "2015-05-29"] - [7 49 #t "2013-09-10"] - [7 49 #t "2013-07-26"] - [15 74 #t "2015-10-26"] - [7 66 #t "2015-07-29"] - [8 93 #t "2015-07-07"] - [13 79 #t "2014-11-12"] - [6 7 #t "2014-12-27"] - [3 80 #t "2015-06-22"] - [13 6 #t "2014-09-09"] - [3 82 #t "2015-06-27"] - [12 13 #t "2013-06-29"] - [14 86 #t "2014-01-07"] - [5 66 #t "2014-05-26"] - [14 62 #t "2013-08-18"] - [10 97 #t "2013-11-19"] - [6 94 #t "2013-04-19"] - [2 41 #t "2014-03-03"] - [13 74 #t "2014-05-26"] - [7 63 #t "2014-05-28"] - [14 31 #t "2013-12-04"] - [13 41 #t "2013-06-15"] - [12 51 #t "2015-12-26"] - [4 65 #t "2015-12-18"] - [5 64 #t "2013-08-02"] - [12 18 #t "2013-10-24"] - [4 38 #t "2014-04-26"] - [7 30 #t "2014-09-18"] - [5 17 #t "2014-05-18"] - [2 76 #t "2015-09-04"] - [13 42 #t "2015-05-26"] - [9 74 #t "2014-08-01"] - [7 42 #t "2013-06-21"] - [3 26 #t "2015-09-28"] - [4 27 #t "2013-05-14"] - [12 21 #t "2013-05-26"] - [13 20 #t "2015-07-15"] - [2 85 #t "2014-05-02"] - [7 52 #t "2014-10-21"] - [5 3 #t "2014-05-04"] - [5 79 #t "2014-07-11"] - [3 10 #t "2014-05-31"] - [9 2 #t "2015-01-28"] - [3 85 #t "2013-11-13"] - [5 40 #t "2015-09-11"] - [11 70 #t "2015-09-20"] - [5 86 #t "2014-12-05"] - [3 86 #t "2014-04-24"] - [5 52 #t "2014-11-05"] - [9 72 #t "2013-11-22"] - [8 27 #t "2015-09-28"] - [8 48 #t "2014-08-02"] - [1 35 #t "2014-05-26"] - [11 6 #t "2014-10-16"] - [1 58 #t "2013-11-18"] - [8 90 #t "2014-08-03"] - [5 47 #t "2013-09-02"] - [11 88 #t "2013-12-11"] - [3 71 #t "2014-09-26"] - [14 66 #t "2015-06-13"] - [6 27 #t "2015-08-16"] - [4 42 #t "2015-01-30"] - [10 67 #t "2014-12-09"] - [3 75 #t "2015-10-08"] - [9 68 #t "2013-11-09"] - [9 87 #t "2014-11-08"] - [5 12 #t "2014-02-05"] - [13 87 #t "2013-04-23"] - [3 72 #t "2015-05-25"] - [3 95 #t "2015-12-18"] - [4 43 #t "2013-04-14"] - [6 17 #t "2014-06-28"] - [12 32 #t "2014-01-05"] - [14 96 #t "2013-04-13"] - [1 76 #t "2015-10-29"] - [5 93 #t "2014-08-21"] - [14 53 #t "2013-11-18"] - [14 20 #t "2014-10-25"] - [3 91 #t "2015-10-19"] - [8 8 #t "2015-11-21"] - [13 34 #t "2013-08-20"] - [2 54 #t "2014-05-08"] - [3 66 #t "2014-10-16"] - [3 57 #t "2014-09-16"] - [10 12 #t "2015-04-12"] - [10 93 #t "2014-01-04"] - [6 20 #t "2014-03-18"] - [14 50 #t "2015-08-20"] - [7 35 #t "2014-07-24"] - [9 25 #t "2014-07-08"] - [13 43 #t "2014-12-23"] - [3 43 #t "2014-10-06"] - [3 58 #t "2014-06-10"] - [8 59 #t "2013-07-05"] - [8 9 #t "2014-03-02"] - [12 11 #t "2013-11-12"] - [8 82 #t "2014-12-19"] - [3 88 #t "2014-03-23"] - [10 81 #t "2015-07-01"] - [4 31 #t "2014-05-01"] - [1 10 #t "2013-03-12"] - [7 98 #t "2015-04-21"] - [10 69 #t "2013-05-03"] - [4 7 #t "2014-11-09"] - [11 57 #t "2014-06-05"] - [4 75 #t "2013-08-20"] - [10 8 #t "2014-10-06"] - [9 48 #t "2015-10-06"] - [14 38 #t "2013-04-14"] - [6 41 #t "2014-10-25"] - [5 14 #t "2013-05-07"] - [11 38 #t "2015-05-13"] - [3 33 #t "2014-11-08"] - [1 72 #t "2013-07-25"] - [10 84 #t "2013-04-07"] - [10 24 #t "2014-06-25"] - [3 50 #t "2013-02-06"] - [14 18 #t "2015-10-28"] - [7 95 #t "2014-10-15"] - [13 86 #t "2014-05-05"] - [14 72 #t "2015-08-05"] - [13 24 #t "2015-10-22"] - [10 19 #t "2014-07-06"] - [1 26 #t "2014-12-31"] - [9 12 #t "2014-06-29"] - [8 32 #t "2013-08-04"] - [3 28 #t "2015-09-19"] - [15 37 #t "2014-10-23"] - [8 8 #t "2014-09-16"] - [7 100 #t "2014-01-19"] - [8 85 #t "2014-03-31"] - [8 23 #t "2014-02-18"] - [4 95 #t "2015-03-03"] - [11 93 #t "2013-10-28"] - [6 75 #t "2014-07-25"] - [10 18 #t "2013-08-27"] - [14 68 #t "2013-02-20"] - [12 13 #t "2015-02-14"] - [4 2 #t "2013-02-27"] - [7 81 #t "2013-04-16"] - [3 21 #t "2013-04-07"] - [6 43 #t "2014-09-30"] - [5 73 #t "2014-11-29"] - [2 38 #t "2014-08-09"] - [14 60 #t "2014-04-29"] - [10 90 #t "2015-12-29"] - [7 3 #t "2015-06-27"] - [2 18 #t "2014-10-14"] - [4 95 #t "2013-05-27"] - [4 65 #t "2014-06-24"] - [10 32 #t "2014-08-02"] - [13 72 #t "2013-02-22"] - [4 9 #t "2014-02-07"] - [12 49 #t "2014-11-18"] - [11 99 #t "2014-06-29"] - [10 30 #t "2014-04-21"] - [12 5 #t "2014-03-26"] - [7 56 #t "2014-01-04"] - [9 16 #t "2013-10-11"] - [6 44 #t "2013-11-11"] - [2 27 #t "2015-03-18"] - [12 25 #t "2014-11-08"] - [1 7 #t "2015-05-29"] - [7 91 #t "2015-06-18"] - [6 89 #t "2015-11-16"] - [8 12 #t "2013-10-01"] - [5 9 #t "2013-04-18"] - [3 81 #t "2014-05-01"] - [7 53 #t "2013-03-26"] - [6 45 #t "2014-02-13"] - [8 84 #t "2015-04-20"] - [5 2 #t "2013-10-02"] - [8 7 #t "2014-09-10"] - [15 41 #t "2013-07-19"] - [13 18 #t "2014-07-24"] - [14 54 #t "2015-09-18"] - [11 84 #t "2014-08-13"] - [7 56 #t "2014-03-29"] - [13 37 #t "2014-05-21"] - [4 96 #t "2014-04-30"] - [6 76 #t "2014-09-16"] - [5 21 #t "2014-07-08"] - [8 61 #t "2014-03-10"] - [5 26 #t "2014-09-05"] - [8 100 #t "2013-05-29"] - [3 47 #t "2014-05-08"] - [7 46 #t "2015-10-04"] - [5 73 #t "2014-02-10"] - [1 54 #t "2014-02-08"] - [12 46 #t "2014-06-29"] - [14 46 #t "2014-10-16"] - [10 69 #t "2015-10-29"] - [1 39 #t "2013-06-03"] - [3 23 #t "2014-03-09"] - [10 43 #t "2014-07-13"] - [14 95 #t "2014-04-17"] - [10 75 #t "2014-03-17"] - [4 50 #t "2013-02-18"] - [12 43 #t "2013-11-01"] - [9 33 #t "2015-07-02"] - [4 91 #t "2013-04-02"] - [15 16 #t "2014-04-12"] - [3 42 #t "2014-02-10"] - [12 65 #t "2014-03-20"] - [13 72 #t "2015-07-22"] - [13 86 #t "2015-05-01"] - [13 93 #t "2013-03-19"] - [10 49 #t "2013-12-19"] - [13 8 #t "2014-12-05"] - [15 52 #t "2015-08-09"] - [7 95 #t "2013-12-11"] - [9 90 #t "2014-10-10"] - [8 50 #t "2015-03-05"] - [6 11 #t "2014-01-12"] - [13 26 #t "2014-08-25"] - [3 39 #t "2014-10-14"] - [8 36 #t "2015-11-13"] - [5 97 #t "2014-05-20"] - [10 35 #t "2014-05-07"] - [11 74 #t "2015-04-06"] - [15 75 #t "2013-04-28"] - [2 88 #t "2014-01-18"] - [9 58 #t "2014-04-16"] - [6 41 #t "2014-11-05"] - [10 44 #t "2015-04-11"] - [10 64 #t "2013-07-20"] - [10 19 #t "2014-02-12"] - [4 13 #t "2014-03-01"] - [13 27 #t "2014-04-02"] - [15 33 #t "2013-03-28"] - [3 6 #t "2015-09-05"] - [7 63 #t "2014-03-08"] - [12 94 #t "2014-09-23"] - [7 38 #t "2014-04-03"] - [11 85 #t "2014-02-17"] - [9 76 #t "2014-07-13"] - [8 83 #t "2014-05-28"] - [14 42 #t "2015-02-03"] - [4 35 #t "2014-03-25"] - [7 58 #t "2014-03-25"] - [3 54 #t "2014-02-25"] - [5 60 #t "2014-12-16"] - [9 100 #t "2014-05-20"] - [12 6 #t "2014-04-09"] - [3 76 #t "2013-07-29"] - [8 73 #t "2013-04-26"] - [13 33 #t "2014-11-03"] - [6 45 #t "2014-05-17"] - [5 87 #t "2014-10-07"] - [5 90 #t "2015-07-21"] - [9 36 #t "2015-08-26"] - [7 57 #t "2015-05-21"] - [9 20 #t "2013-10-03"] - [4 13 #t "2013-05-18"] - [13 63 #t "2014-03-22"] - [5 42 #t "2015-08-22"] - [9 49 #t "2015-03-02"] - [6 15 #t "2014-10-02"] - [7 17 #t "2013-07-18"] - [11 63 #t "2014-02-17"] - [3 90 #t "2013-02-26"] - [13 90 #t "2013-06-08"] - [6 46 #t "2014-03-24"] - [15 91 #t "2014-02-19"] - [10 65 #t "2014-10-10"] - [3 76 #t "2014-05-08"] - [13 43 #t "2014-02-11"] - [7 15 #t "2015-07-09"] - [1 36 #t "2014-03-03"] - [9 59 #t "2014-08-03"] - [5 86 #t "2015-04-02"] - [14 63 #t "2014-07-11"] - [5 94 #t "2013-11-24"] - [14 54 #t "2014-08-03"] - [2 37 #t "2014-08-02"] - [4 62 #t "2015-11-08"] - [7 17 #t "2013-10-01"] - [13 59 #t "2014-01-03"] - [4 22 #t "2013-03-14"] - [3 94 #t "2015-12-16"] - [14 89 #t "2014-06-06"] - [15 23 #t "2015-08-19"] - [8 12 #t "2015-03-17"] - [8 93 #t "2015-05-29"] - [3 20 #t "2013-05-20"] - [9 2 #t "2013-04-03"] - [13 73 #t "2014-06-30"] - [10 5 #t "2015-05-04"] - [4 98 #t "2014-08-29"] - [5 38 #t "2015-04-15"] - [3 41 #t "2014-10-19"] - [2 53 #t "2013-06-21"] - [12 97 #t "2015-11-03"] - [15 68 #t "2013-05-06"] - [15 22 #t "2013-08-16"] - [11 57 #t "2013-05-01"] - [6 91 #t "2015-02-22"] - [2 63 #t "2014-04-22"] - [13 70 #t "2013-03-06"] - [11 86 #t "2013-11-02"] - [13 23 #t "2015-05-26"] - [12 5 #t "2013-11-17"] - [5 43 #t "2015-12-02"] - [11 96 #t "2014-03-26"] - [2 90 #t "2013-12-03"] - [10 21 #t "2014-08-24"] - [11 20 #t "2014-10-28"] - [13 44 #t "2015-09-11"] - [11 57 #t "2014-08-02"] - [6 45 #t "2014-09-29"] - [13 68 #t "2014-08-03"] - [9 75 #t "2015-07-24"] - [7 37 #t "2014-10-06"] - [6 21 #t "2014-11-06"] - [14 49 #t "2013-11-22"] - [7 67 #t "2014-08-03"] - [4 75 #t "2014-10-20"] - [13 13 #t "2015-08-26"] - [2 58 #t "2014-09-01"] - [2 23 #t "2013-03-19"] - [4 38 #t "2014-10-03"] - [7 26 #t "2015-10-06"] - [2 93 #t "2014-10-23"] - [2 41 #t "2014-07-02"] - [7 99 #t "2014-10-18"] - [14 64 #t "2014-09-10"] - [10 9 #t "2014-10-25"] - [6 48 #t "2014-12-25"] - [8 58 #t "2014-02-18"] - [3 35 #t "2014-08-25"] - [6 98 #t "2014-07-01"] - [8 97 #t "2013-09-16"] - [13 26 #t "2014-09-22"] - [2 91 #t "2014-04-15"] - [6 20 #t "2015-06-30"] - [15 74 #t "2014-06-13"] - [7 62 #t "2014-10-13"] - [7 95 #t "2014-06-03"] - [1 96 #t "2014-10-16"] - [9 84 #t "2013-10-20"] - [4 55 #t "2014-09-24"] - [13 86 #t "2014-02-16"] - [14 9 #t "2015-02-05"] - [9 37 #t "2014-06-19"] - [3 12 #t "2015-11-13"] - [10 91 #t "2014-06-10"] - [1 13 #t "2013-10-29"] - [4 57 #t "2013-05-10"] - [5 57 #t "2014-05-28"] - [14 31 #t "2013-06-18"] - [3 29 #t "2014-06-16"] - [7 93 #t "2015-06-07"] - [7 87 #t "2015-11-21"] - [9 53 #t "2015-09-26"] - [14 93 #t "2014-10-20"] - [14 37 #t "2014-08-14"] - [3 30 #t "2013-03-21"] - [10 82 #t "2013-06-05"] - [4 40 #t "2015-07-17"] - [8 45 #t "2014-09-08"] - [6 84 #t "2013-02-15"] - [11 87 #t "2015-11-06"] - [10 93 #t "2014-12-24"] - [2 54 #t "2014-08-02"] - [3 34 #t "2014-05-07"] - [13 48 #t "2014-10-01"] - [4 48 #t "2014-10-24"] - [1 46 #t "2015-04-25"] - [14 85 #t "2015-03-15"] - [4 37 #t "2014-03-05"] - [6 62 #t "2014-02-20"] - [2 73 #t "2014-08-20"] - [2 14 #t "2013-09-29"] - [6 83 #t "2013-09-01"] - [11 89 #t "2013-10-16"] - [3 58 #t "2013-12-04"] - [3 36 #t "2014-06-22"] - [5 96 #t "2015-06-26"] - [5 18 #t "2014-04-22"] - [4 54 #t "2014-10-29"] - [9 31 #t "2013-09-29"] - [12 49 #t "2015-04-19"] - [3 38 #t "2013-01-26"] - [4 88 #t "2013-01-03"] - [12 58 #t "2015-11-25"] - [12 58 #t "2015-08-24"] - [15 3 #t "2015-05-22"] - [10 17 #t "2013-05-04"] - [6 85 #t "2013-08-10"] - [7 18 #t "2015-07-09"] - [12 67 #t "2015-06-15"] - [8 96 #t "2015-02-22"] - [15 88 #t "2015-02-13"] - [8 70 #t "2015-12-22"] - [8 48 #t "2014-10-04"] - [3 91 #t "2013-06-05"] - [8 83 #t "2014-11-06"] - [12 5 #t "2013-11-28"] - [13 88 #t "2014-03-29"] - [2 73 #t "2014-11-02"] - [7 13 #t "2013-10-22"] - [13 17 #t "2015-06-16"] - [7 11 #t "2014-03-09"] - [2 84 #t "2014-03-06"] - [8 79 #t "2014-06-13"] - [2 77 #t "2014-04-10"] - [3 40 #t "2014-05-11"] - [8 30 #t "2013-03-06"] - [1 47 #t "2014-12-07"] - [11 49 #t "2014-12-21"] - [5 39 #t "2014-10-31"] - [3 98 #t "2014-10-22"] - [9 20 #t "2015-04-09"] - [13 66 #t "2013-07-23"] - [15 18 #t "2013-04-26"] - [9 37 #t "2013-02-06"] - [12 79 #t "2014-09-07"] - [8 49 #t "2014-04-26"] - [6 87 #t "2015-07-01"] - [2 70 #t "2015-09-27"] - [7 44 #t "2014-11-05"] - [6 65 #t "2014-11-27"] - [8 51 #t "2015-09-07"] - [6 11 #t "2015-08-21"] - [11 76 #t "2014-05-21"] - [5 94 #t "2014-09-20"] - [1 97 #t "2015-04-05"] - [2 20 #t "2014-11-21"] - [9 25 #t "2014-06-03"] - [4 10 #t "2013-09-21"] - [14 78 #t "2013-09-14"] - [6 34 #t "2014-05-30"] - [1 16 #t "2014-03-30"] - [15 36 #t "2014-09-23"] - [8 5 #t "2013-08-21"] - [11 39 #t "2014-10-10"] - [4 66 #t "2014-03-16"] - [12 74 #t "2014-10-07"] - [6 76 #t "2015-08-09"] - [14 62 #t "2015-07-22"] - [14 98 #t "2015-08-13"] - [8 40 #t "2014-04-03"] - [3 33 #t "2014-11-13"] - [12 42 #t "2014-05-09"] - [8 77 #t "2015-09-24"] - [2 16 #t "2014-12-09"] - [4 29 #t "2015-05-29"] - [11 49 #t "2014-03-05"] - [13 58 #t "2014-04-29"] - [9 34 #t "2014-05-04"] - [12 5 #t "2015-04-16"] - [7 67 #t "2015-02-07"] - [2 92 #t "2014-06-03"]]]] + :fk :venues}] + [[#t "2014-04-07" 5 12] + [#t "2014-09-18" 1 31] + [#t "2014-09-15" 8 56] + [#t "2014-03-11" 5 4] + [#t "2013-05-05" 3 49] + [#t "2015-07-04" 3 35] + [#t "2014-04-11" 12 6] + [#t "2014-05-13" 11 16] + [#t "2014-05-26" 3 79] + [#t "2015-08-22" 3 55] + [#t "2013-03-25" 15 23] + [#t "2014-11-16" 5 25] + [#t "2014-05-17" 6 80] + [#t "2015-09-07" 9 93] + [#t "2015-02-19" 5 61] + [#t "2015-02-19" 11 34] + [#t "2014-08-31" 8 69] + [#t "2015-08-05" 3 27] + [#t "2014-07-31" 11 70] + [#t "2014-09-14" 11 81] + [#t "2015-12-10" 4 73] + [#t "2015-02-17" 8 18] + [#t "2014-04-02" 8 71] + [#t "2014-04-04" 12 45] + [#t "2014-07-05" 12 31] + [#t "2014-05-17" 8 67] + [#t "2015-02-15" 11 57] + [#t "2015-05-02" 10 100] + [#t "2014-09-27" 15 7] + [#t "2014-07-20" 5 48] + [#t "2013-05-12" 8 27] + [#t "2014-02-17" 14 31] + [#t "2015-10-07" 5 6] + [#t "2014-09-26" 14 28] + [#t "2014-07-19" 10 56] + [#t "2015-09-29" 8 19] + [#t "2015-11-19" 4 48] + [#t "2015-11-15" 10 11] + [#t "2015-04-30" 11 65] + [#t "2014-09-14" 6 6] + [#t "2013-07-05" 10 62] + [#t "2015-03-05" 11 88] + [#t "2015-11-30" 5 51] + [#t "2013-11-19" 2 61] + [#t "2013-08-24" 9 59] + [#t "2015-06-26" 9 66] + [#t "2014-07-19" 14 67] + [#t "2015-07-18" 12 15] + [#t "2015-04-07" 5 12] + [#t "2013-08-02" 8 13] + [#t "2014-06-28" 13 30] + [#t "2014-10-13" 4 8] + [#t "2014-05-05" 6 65] + [#t "2015-05-08" 4 93] + [#t "2014-12-22" 15 14] + [#t "2014-07-24" 3 99] + [#t "2015-03-06" 2 15] + [#t "2015-11-09" 4 40] + [#t "2014-06-11" 13 24] + [#t "2014-03-24" 10 69] + [#t "2013-11-01" 3 15] + [#t "2013-05-28" 10 3] + [#t "2015-12-24" 5 15] + [#t "2014-08-18" 6 34] + [#t "2014-04-13" 7 29] + [#t "2015-02-21" 6 86] + [#t "2014-05-25" 3 28] + [#t "2014-09-16" 5 29] + [#t "2014-03-14" 8 85] + [#t "2014-11-20" 11 65] + [#t "2014-01-07" 14 93] + [#t "2015-04-18" 1 1] + [#t "2013-08-07" 11 75] + [#t "2015-11-14" 11 91] + [#t "2015-09-11" 7 97] + [#t "2014-03-28" 9 9] + [#t "2014-03-03" 14 33] + [#t "2015-03-02" 4 3] + [#t "2014-05-07" 8 19] + [#t "2013-12-27" 1 99] + [#t "2013-07-23" 7 18] + [#t "2014-06-28" 13 30] + [#t "2015-02-19" 11 17] + [#t "2015-12-02" 12 58] + [#t "2014-11-15" 13 82] + [#t "2015-07-01" 8 69] + [#t "2014-08-18" 14 95] + [#t "2015-05-29" 6 75] + [#t "2013-10-29" 14 83] + [#t "2014-02-10" 7 66] + [#t "2015-04-09" 11 73] + [#t "2014-08-02" 4 93] + [#t "2013-07-02" 2 18] + [#t "2015-02-09" 10 17] + [#t "2013-10-27" 8 60] + [#t "2014-02-11" 13 24] + [#t "2014-11-25" 2 71] + [#t "2014-01-04" 11 29] + [#t "2015-11-27" 13 91] + [#t "2014-07-26" 9 21] + [#t "2015-09-04" 11 99] + [#t "2013-12-02" 5 44] + [#t "2015-04-10" 8 51] + [#t "2015-09-26" 8 9] + [#t "2014-12-01" 11 19] + [#t "2014-02-10" 2 38] + [#t "2014-07-14" 10 12] + [#t "2013-12-15" 10 30] + [#t "2013-03-16" 8 33] + [#t "2015-09-17" 9 49] + [#t "2013-05-19" 6 38] + [#t "2014-03-12" 15 93] + [#t "2013-01-27" 2 36] + [#t "2015-09-17" 7 98] + [#t "2015-09-18" 4 6] + [#t "2013-09-16" 6 34] + [#t "2014-02-20" 11 73] + [#t "2014-07-05" 14 46] + [#t "2014-10-18" 1 44] + [#t "2013-12-22" 10 83] + [#t "2014-11-05" 3 21] + [#t "2014-12-19" 12 57] + [#t "2015-07-25" 10 77] + [#t "2013-08-05" 10 97] + [#t "2015-04-16" 14 8] + [#t "2015-09-11" 12 13] + [#t "2015-10-29" 15 81] + [#t "2014-08-18" 13 17] + [#t "2014-08-25" 15 2] + [#t "2014-08-11" 8 74] + [#t "2013-02-13" 7 90] + [#t "2014-08-26" 4 84] + [#t "2014-01-09" 10 87] + [#t "2013-08-21" 8 88] + [#t "2015-12-26" 6 85] + [#t "2014-05-21" 8 62] + [#t "2015-02-15" 4 97] + [#t "2014-02-11" 4 65] + [#t "2015-10-08" 9 1] + [#t "2014-10-10" 13 96] + [#t "2015-11-22" 10 83] + [#t "2013-07-24" 15 24] + [#t "2014-09-14" 10 38] + [#t "2015-11-13" 12 3] + [#t "2014-10-13" 4 78] + [#t "2014-07-17" 14 12] + [#t "2014-05-31" 3 18] + [#t "2013-10-19" 11 10] + [#t "2013-03-13" 11 81] + [#t "2014-11-21" 5 61] + [#t "2014-12-17" 13 81] + [#t "2014-09-04" 13 24] + [#t "2013-02-21" 8 54] + [#t "2014-07-08" 10 42] + [#t "2014-09-23" 7 97] + [#t "2013-03-08" 3 14] + [#t "2014-01-31" 12 58] + [#t "2015-09-04" 11 100] + [#t "2014-03-06" 8 90] + [#t "2015-05-05" 12 20] + [#t "2014-06-30" 3 47] + [#t "2015-01-04" 5 44] + [#t "2013-09-10" 1 47] + [#t "2015-05-22" 7 11] + [#t "2013-12-29" 15 87] + [#t "2014-07-17" 2 66] + [#t "2014-07-09" 2 52] + [#t "2015-06-29" 11 3] + [#t "2013-09-16" 9 12] + [#t "2014-07-17" 2 39] + [#t "2014-03-13" 11 3] + [#t "2015-03-04" 10 47] + [#t "2015-08-21" 15 78] + [#t "2014-04-19" 12 3] + [#t "2014-11-07" 11 45] + [#t "2013-05-03" 2 75] + [#t "2013-03-08" 10 9] + [#t "2013-10-04" 2 51] + [#t "2014-07-10" 4 4] + [#t "2014-07-16" 8 57] + [#t "2014-08-15" 8 38] + [#t "2014-01-06" 5 31] + [#t "2015-05-27" 3 54] + [#t "2015-07-05" 10 84] + [#t "2013-03-01" 5 54] + [#t "2015-08-06" 3 43] + [#t "2014-11-09" 8 70] + [#t "2014-07-30" 2 59] + [#t "2015-01-14" 11 18] + [#t "2013-06-30" 9 75] + [#t "2015-08-01" 13 41] + [#t "2014-02-06" 7 87] + [#t "2013-03-19" 6 91] + [#t "2013-06-23" 7 90] + [#t "2014-04-11" 8 61] + [#t "2014-10-28" 5 35] + [#t "2014-06-09" 2 82] + [#t "2013-10-23" 9 35] + [#t "2014-09-28" 6 52] + [#t "2015-09-04" 6 96] + [#t "2015-09-18" 11 59] + [#t "2015-08-09" 12 34] + [#t "2015-04-08" 10 80] + [#t "2015-03-01" 4 78] + [#t "2015-09-01" 6 4] + [#t "2014-07-07" 4 35] + [#t "2013-06-23" 13 50] + [#t "2014-03-17" 11 69] + [#t "2013-03-15" 10 62] + [#t "2015-03-27" 13 31] + [#t "2014-10-02" 13 42] + [#t "2013-05-14" 14 86] + [#t "2014-08-13" 4 83] + [#t "2015-04-18" 9 21] + [#t "2014-06-15" 14 90] + [#t "2015-05-05" 12 65] + [#t "2013-03-08" 7 57] + [#t "2014-09-06" 12 70] + [#t "2014-02-09" 11 16] + [#t "2013-10-12" 7 38] + [#t "2014-03-09" 2 40] + [#t "2015-06-22" 8 52] + [#t "2013-01-22" 7 84] + [#t "2014-08-20" 9 4] + [#t "2014-03-04" 2 4] + [#t "2014-02-03" 8 56] + [#t "2013-10-29" 6 23] + [#t "2013-10-02" 7 87] + [#t "2014-02-14" 5 28] + [#t "2013-09-08" 5 19] + [#t "2014-06-30" 2 13] + [#t "2013-07-25" 12 65] + [#t "2015-11-06" 4 73] + [#t "2013-07-14" 7 56] + [#t "2014-03-09" 1 46] + [#t "2013-07-10" 13 58] + [#t "2013-04-12" 4 68] + [#t "2014-03-09" 14 86] + [#t "2014-11-22" 7 89] + [#t "2014-04-13" 4 42] + [#t "2014-10-19" 13 83] + [#t "2014-07-07" 10 66] + [#t "2013-08-19" 11 69] + [#t "2014-11-28" 2 18] + [#t "2015-08-16" 12 7] + [#t "2014-03-20" 7 45] + [#t "2014-10-09" 8 85] + [#t "2014-05-16" 13 27] + [#t "2014-08-24" 8 6] + [#t "2013-04-11" 9 52] + [#t "2015-02-26" 2 75] + [#t "2014-05-29" 11 65] + [#t "2013-06-03" 7 30] + [#t "2013-06-26" 11 14] + [#t "2014-11-09" 11 61] + [#t "2013-06-27" 8 81] + [#t "2014-05-07" 10 92] + [#t "2014-01-26" 3 52] + [#t "2014-11-14" 5 56] + [#t "2014-04-02" 11 75] + [#t "2014-10-22" 9 13] + [#t "2015-05-18" 4 25] + [#t "2015-05-04" 10 70] + [#t "2014-06-21" 2 48] + [#t "2014-03-28" 6 78] + [#t "2014-10-24" 12 68] + [#t "2014-02-26" 10 8] + [#t "2015-10-12" 5 63] + [#t "2015-08-06" 14 66] + [#t "2014-05-14" 2 3] + [#t "2014-04-27" 3 36] + [#t "2015-04-24" 11 71] + [#t "2015-03-02" 1 85] + [#t "2015-06-22" 13 68] + [#t "2014-09-25" 5 34] + [#t "2014-07-29" 2 75] + [#t "2014-10-03" 7 13] + [#t "2014-01-10" 12 86] + [#t "2015-07-17" 13 100] + [#t "2014-10-15" 8 59] + [#t "2014-05-17" 15 27] + [#t "2013-10-11" 13 83] + [#t "2014-06-03" 2 68] + [#t "2013-04-08" 10 23] + [#t "2013-01-10" 12 17] + [#t "2014-05-01" 8 89] + [#t "2013-06-12" 4 11] + [#t "2015-05-22" 3 97] + [#t "2013-10-19" 14 77] + [#t "2014-10-10" 10 69] + [#t "2014-11-11" 13 79] + [#t "2014-07-22" 5 95] + [#t "2015-08-26" 14 33] + [#t "2014-07-10" 2 75] + [#t "2014-03-01" 7 97] + [#t "2014-08-04" 6 88] + [#t "2013-11-16" 12 73] + [#t "2013-05-21" 14 61] + [#t "2015-03-07" 9 43] + [#t "2013-08-20" 4 44] + [#t "2015-07-18" 15 61] + [#t "2014-09-29" 11 98] + [#t "2013-08-02" 11 32] + [#t "2014-12-06" 3 94] + [#t "2015-08-20" 4 48] + [#t "2014-07-21" 11 59] + [#t "2014-06-05" 9 77] + [#t "2015-04-23" 7 10] + [#t "2013-04-01" 7 17] + [#t "2015-02-13" 9 45] + [#t "2014-12-23" 3 2] + [#t "2014-12-11" 9 85] + [#t "2015-04-24" 6 22] + [#t "2013-06-11" 12 96] + [#t "2014-06-09" 1 78] + [#t "2014-02-10" 13 29] + [#t "2013-01-19" 3 54] + [#t "2014-08-30" 11 60] + [#t "2013-11-27" 2 16] + [#t "2014-05-14" 9 41] + [#t "2014-01-26" 10 98] + [#t "2015-07-01" 13 98] + [#t "2015-10-04" 12 4] + [#t "2014-03-14" 1 63] + [#t "2015-10-23" 11 2] + [#t "2014-05-27" 14 64] + [#t "2014-05-01" 6 42] + [#t "2014-09-26" 2 44] + [#t "2013-08-15" 5 42] + [#t "2013-04-26" 5 39] + [#t "2014-07-14" 11 94] + [#t "2015-08-18" 4 17] + [#t "2014-02-19" 9 3] + [#t "2014-05-18" 3 75] + [#t "2015-09-22" 10 38] + [#t "2013-03-28" 10 74] + [#t "2014-01-04" 11 25] + [#t "2014-09-14" 7 20] + [#t "2014-05-18" 7 33] + [#t "2013-02-19" 2 40] + [#t "2013-06-16" 9 62] + [#t "2014-11-25" 6 5] + [#t "2014-07-19" 14 13] + [#t "2013-04-24" 4 82] + [#t "2014-04-10" 6 86] + [#t "2013-05-31" 15 66] + [#t "2014-09-03" 10 63] + [#t "2014-06-25" 13 46] + [#t "2015-02-24" 13 44] + [#t "2014-06-19" 5 82] + [#t "2014-09-12" 12 57] + [#t "2015-03-16" 5 96] + [#t "2015-05-11" 12 36] + [#t "2015-11-28" 6 100] + [#t "2014-10-24" 9 44] + [#t "2014-04-03" 13 70] + [#t "2014-04-12" 10 77] + [#t "2015-07-23" 13 42] + [#t "2013-06-02" 6 99] + [#t "2015-07-06" 9 22] + [#t "2014-12-16" 13 83] + [#t "2014-07-20" 13 27] + [#t "2015-11-05" 10 94] + [#t "2015-01-23" 13 70] + [#t "2014-05-20" 5 59] + [#t "2013-01-25" 12 61] + [#t "2015-07-23" 1 5] + [#t "2013-08-19" 5 95] + [#t "2014-07-25" 6 88] + [#t "2013-11-07" 3 54] + [#t "2015-07-20" 11 57] + [#t "2014-08-26" 7 27] + [#t "2015-05-03" 2 62] + [#t "2014-04-14" 4 36] + [#t "2013-11-09" 12 1] + [#t "2013-07-14" 4 27] + [#t "2013-11-15" 10 63] + [#t "2014-05-22" 6 31] + [#t "2015-10-04" 2 90] + [#t "2013-12-03" 8 2] + [#t "2015-09-23" 1 86] + [#t "2014-09-05" 7 46] + [#t "2015-07-29" 9 8] + [#t "2014-05-30" 1 51] + [#t "2014-03-25" 7 68] + [#t "2015-02-23" 14 74] + [#t "2015-05-12" 8 63] + [#t "2013-04-11" 1 68] + [#t "2015-03-28" 7 20] + [#t "2014-07-24" 14 28] + [#t "2014-02-03" 13 8] + [#t "2013-09-27" 3 40] + [#t "2014-01-19" 9 72] + [#t "2013-05-14" 11 15] + [#t "2014-03-30" 9 11] + [#t "2014-12-03" 4 10] + [#t "2014-11-07" 4 79] + [#t "2013-05-09" 4 74] + [#t "2015-12-08" 5 78] + [#t "2015-06-04" 12 74] + [#t "2014-06-14" 6 89] + [#t "2013-01-23" 2 87] + [#t "2015-07-18" 15 84] + [#t "2015-03-11" 4 65] + [#t "2013-07-25" 7 66] + [#t "2013-11-29" 10 14] + [#t "2014-06-02" 5 77] + [#t "2013-04-30" 8 74] + [#t "2014-03-05" 14 7] + [#t "2013-11-05" 4 45] + [#t "2013-11-26" 15 96] + [#t "2015-01-15" 4 45] + [#t "2015-05-01" 15 52] + [#t "2014-02-25" 6 46] + [#t "2014-11-10" 12 42] + [#t "2014-05-20" 13 17] + [#t "2015-03-27" 6 44] + [#t "2014-04-14" 3 71] + [#t "2013-10-01" 2 35] + [#t "2015-03-03" 9 74] + [#t "2014-12-01" 4 68] + [#t "2013-11-25" 6 40] + [#t "2014-06-14" 4 63] + [#t "2013-08-05" 11 12] + [#t "2015-02-03" 13 41] + [#t "2014-10-05" 11 13] + [#t "2015-03-20" 10 18] + [#t "2014-05-11" 5 20] + [#t "2014-02-22" 5 79] + [#t "2013-04-15" 7 15] + [#t "2014-03-28" 6 25] + [#t "2014-08-12" 14 9] + [#t "2014-04-24" 8 53] + [#t "2014-07-02" 9 78] + [#t "2014-06-26" 3 4] + [#t "2015-10-29" 7 3] + [#t "2015-10-28" 6 56] + [#t "2014-08-12" 4 65] + [#t "2014-07-28" 15 35] + [#t "2014-09-01" 8 49] + [#t "2014-07-31" 11 80] + [#t "2015-03-01" 10 51] + [#t "2013-07-15" 14 70] + [#t "2013-10-06" 12 18] + [#t "2013-10-31" 8 80] + [#t "2013-11-16" 15 91] + [#t "2014-09-06" 9 78] + [#t "2013-06-04" 9 88] + [#t "2014-05-29" 12 88] + [#t "2013-06-07" 7 22] + [#t "2014-06-21" 2 38] + [#t "2014-05-07" 4 7] + [#t "2013-04-18" 2 49] + [#t "2014-07-19" 13 56] + [#t "2013-06-14" 9 66] + [#t "2014-07-29" 9 57] + [#t "2014-04-04" 5 91] + [#t "2015-06-08" 10 46] + [#t "2014-08-01" 10 97] + [#t "2014-07-04" 2 53] + [#t "2015-07-27" 14 54] + [#t "2013-08-17" 2 81] + [#t "2015-07-12" 11 77] + [#t "2013-08-03" 13 39] + [#t "2014-01-16" 7 86] + [#t "2014-05-07" 14 68] + [#t "2014-05-29" 13 61] + [#t "2015-09-16" 6 90] + [#t "2014-10-13" 11 59] + [#t "2015-11-07" 11 41] + [#t "2015-04-23" 12 2] + [#t "2013-10-18" 10 76] + [#t "2014-02-04" 14 77] + [#t "2014-01-04" 2 80] + [#t "2015-08-15" 3 65] + [#t "2013-04-03" 9 59] + [#t "2015-11-10" 11 6] + [#t "2013-06-30" 9 29] + [#t "2014-06-27" 10 37] + [#t "2013-12-11" 2 26] + [#t "2013-03-05" 3 28] + [#t "2015-03-13" 2 94] + [#t "2015-10-05" 11 72] + [#t "2014-08-15" 7 39] + [#t "2014-05-18" 9 6] + [#t "2013-04-26" 10 98] + [#t "2014-02-18" 9 92] + [#t "2014-08-09" 13 39] + [#t "2014-08-12" 9 21] + [#t "2014-01-27" 2 60] + [#t "2013-07-22" 6 58] + [#t "2014-05-16" 8 41] + [#t "2014-08-30" 6 15] + [#t "2015-04-21" 12 7] + [#t "2014-09-01" 7 14] + [#t "2014-11-27" 10 43] + [#t "2014-05-22" 14 22] + [#t "2015-11-22" 2 48] + [#t "2014-02-12" 9 92] + [#t "2015-10-26" 8 48] + [#t "2015-05-09" 13 97] + [#t "2014-07-14" 6 12] + [#t "2013-06-17" 11 22] + [#t "2013-03-23" 4 23] + [#t "2014-06-22" 10 68] + [#t "2014-07-16" 5 78] + [#t "2015-03-01" 4 32] + [#t "2014-05-23" 10 33] + [#t "2013-07-07" 10 6] + [#t "2015-04-04" 7 98] + [#t "2015-02-03" 14 29] + [#t "2014-09-08" 2 53] + [#t "2014-07-30" 9 43] + [#t "2015-11-01" 14 74] + [#t "2014-09-27" 3 94] + [#t "2015-09-27" 11 86] + [#t "2014-11-07" 1 50] + [#t "2013-06-19" 4 43] + [#t "2015-07-28" 2 85] + [#t "2014-11-09" 5 24] + [#t "2014-08-26" 14 88] + [#t "2014-08-09" 6 61] + [#t "2015-12-19" 2 83] + [#t "2015-07-25" 1 38] + [#t "2015-01-25" 6 49] + [#t "2015-02-09" 12 31] + [#t "2014-11-28" 6 61] + [#t "2013-06-12" 5 50] + [#t "2014-11-03" 7 81] + [#t "2014-03-27" 9 48] + [#t "2014-09-24" 6 72] + [#t "2013-10-06" 4 59] + [#t "2014-04-18" 8 48] + [#t "2015-04-10" 11 88] + [#t "2014-02-28" 10 67] + [#t "2014-01-18" 2 74] + [#t "2014-12-07" 10 70] + [#t "2014-11-07" 4 53] + [#t "2015-02-18" 8 81] + [#t "2014-05-05" 3 72] + [#t "2014-06-17" 15 72] + [#t "2015-06-13" 4 8] + [#t "2014-11-30" 8 73] + [#t "2014-09-20" 8 93] + [#t "2014-01-21" 14 44] + [#t "2014-06-05" 8 68] + [#t "2013-05-20" 5 94] + [#t "2015-05-29" 3 7] + [#t "2013-09-10" 7 49] + [#t "2013-07-26" 7 49] + [#t "2015-10-26" 15 74] + [#t "2015-07-29" 7 66] + [#t "2015-07-07" 8 93] + [#t "2014-11-12" 13 79] + [#t "2014-12-27" 6 7] + [#t "2015-06-22" 3 80] + [#t "2014-09-09" 13 6] + [#t "2015-06-27" 3 82] + [#t "2013-06-29" 12 13] + [#t "2014-01-07" 14 86] + [#t "2014-05-26" 5 66] + [#t "2013-08-18" 14 62] + [#t "2013-11-19" 10 97] + [#t "2013-04-19" 6 94] + [#t "2014-03-03" 2 41] + [#t "2014-05-26" 13 74] + [#t "2014-05-28" 7 63] + [#t "2013-12-04" 14 31] + [#t "2013-06-15" 13 41] + [#t "2015-12-26" 12 51] + [#t "2015-12-18" 4 65] + [#t "2013-08-02" 5 64] + [#t "2013-10-24" 12 18] + [#t "2014-04-26" 4 38] + [#t "2014-09-18" 7 30] + [#t "2014-05-18" 5 17] + [#t "2015-09-04" 2 76] + [#t "2015-05-26" 13 42] + [#t "2014-08-01" 9 74] + [#t "2013-06-21" 7 42] + [#t "2015-09-28" 3 26] + [#t "2013-05-14" 4 27] + [#t "2013-05-26" 12 21] + [#t "2015-07-15" 13 20] + [#t "2014-05-02" 2 85] + [#t "2014-10-21" 7 52] + [#t "2014-05-04" 5 3] + [#t "2014-07-11" 5 79] + [#t "2014-05-31" 3 10] + [#t "2015-01-28" 9 2] + [#t "2013-11-13" 3 85] + [#t "2015-09-11" 5 40] + [#t "2015-09-20" 11 70] + [#t "2014-12-05" 5 86] + [#t "2014-04-24" 3 86] + [#t "2014-11-05" 5 52] + [#t "2013-11-22" 9 72] + [#t "2015-09-28" 8 27] + [#t "2014-08-02" 8 48] + [#t "2014-05-26" 1 35] + [#t "2014-10-16" 11 6] + [#t "2013-11-18" 1 58] + [#t "2014-08-03" 8 90] + [#t "2013-09-02" 5 47] + [#t "2013-12-11" 11 88] + [#t "2014-09-26" 3 71] + [#t "2015-06-13" 14 66] + [#t "2015-08-16" 6 27] + [#t "2015-01-30" 4 42] + [#t "2014-12-09" 10 67] + [#t "2015-10-08" 3 75] + [#t "2013-11-09" 9 68] + [#t "2014-11-08" 9 87] + [#t "2014-02-05" 5 12] + [#t "2013-04-23" 13 87] + [#t "2015-05-25" 3 72] + [#t "2015-12-18" 3 95] + [#t "2013-04-14" 4 43] + [#t "2014-06-28" 6 17] + [#t "2014-01-05" 12 32] + [#t "2013-04-13" 14 96] + [#t "2015-10-29" 1 76] + [#t "2014-08-21" 5 93] + [#t "2013-11-18" 14 53] + [#t "2014-10-25" 14 20] + [#t "2015-10-19" 3 91] + [#t "2015-11-21" 8 8] + [#t "2013-08-20" 13 34] + [#t "2014-05-08" 2 54] + [#t "2014-10-16" 3 66] + [#t "2014-09-16" 3 57] + [#t "2015-04-12" 10 12] + [#t "2014-01-04" 10 93] + [#t "2014-03-18" 6 20] + [#t "2015-08-20" 14 50] + [#t "2014-07-24" 7 35] + [#t "2014-07-08" 9 25] + [#t "2014-12-23" 13 43] + [#t "2014-10-06" 3 43] + [#t "2014-06-10" 3 58] + [#t "2013-07-05" 8 59] + [#t "2014-03-02" 8 9] + [#t "2013-11-12" 12 11] + [#t "2014-12-19" 8 82] + [#t "2014-03-23" 3 88] + [#t "2015-07-01" 10 81] + [#t "2014-05-01" 4 31] + [#t "2013-03-12" 1 10] + [#t "2015-04-21" 7 98] + [#t "2013-05-03" 10 69] + [#t "2014-11-09" 4 7] + [#t "2014-06-05" 11 57] + [#t "2013-08-20" 4 75] + [#t "2014-10-06" 10 8] + [#t "2015-10-06" 9 48] + [#t "2013-04-14" 14 38] + [#t "2014-10-25" 6 41] + [#t "2013-05-07" 5 14] + [#t "2015-05-13" 11 38] + [#t "2014-11-08" 3 33] + [#t "2013-07-25" 1 72] + [#t "2013-04-07" 10 84] + [#t "2014-06-25" 10 24] + [#t "2013-02-06" 3 50] + [#t "2015-10-28" 14 18] + [#t "2014-10-15" 7 95] + [#t "2014-05-05" 13 86] + [#t "2015-08-05" 14 72] + [#t "2015-10-22" 13 24] + [#t "2014-07-06" 10 19] + [#t "2014-12-31" 1 26] + [#t "2014-06-29" 9 12] + [#t "2013-08-04" 8 32] + [#t "2015-09-19" 3 28] + [#t "2014-10-23" 15 37] + [#t "2014-09-16" 8 8] + [#t "2014-01-19" 7 100] + [#t "2014-03-31" 8 85] + [#t "2014-02-18" 8 23] + [#t "2015-03-03" 4 95] + [#t "2013-10-28" 11 93] + [#t "2014-07-25" 6 75] + [#t "2013-08-27" 10 18] + [#t "2013-02-20" 14 68] + [#t "2015-02-14" 12 13] + [#t "2013-02-27" 4 2] + [#t "2013-04-16" 7 81] + [#t "2013-04-07" 3 21] + [#t "2014-09-30" 6 43] + [#t "2014-11-29" 5 73] + [#t "2014-08-09" 2 38] + [#t "2014-04-29" 14 60] + [#t "2015-12-29" 10 90] + [#t "2015-06-27" 7 3] + [#t "2014-10-14" 2 18] + [#t "2013-05-27" 4 95] + [#t "2014-06-24" 4 65] + [#t "2014-08-02" 10 32] + [#t "2013-02-22" 13 72] + [#t "2014-02-07" 4 9] + [#t "2014-11-18" 12 49] + [#t "2014-06-29" 11 99] + [#t "2014-04-21" 10 30] + [#t "2014-03-26" 12 5] + [#t "2014-01-04" 7 56] + [#t "2013-10-11" 9 16] + [#t "2013-11-11" 6 44] + [#t "2015-03-18" 2 27] + [#t "2014-11-08" 12 25] + [#t "2015-05-29" 1 7] + [#t "2015-06-18" 7 91] + [#t "2015-11-16" 6 89] + [#t "2013-10-01" 8 12] + [#t "2013-04-18" 5 9] + [#t "2014-05-01" 3 81] + [#t "2013-03-26" 7 53] + [#t "2014-02-13" 6 45] + [#t "2015-04-20" 8 84] + [#t "2013-10-02" 5 2] + [#t "2014-09-10" 8 7] + [#t "2013-07-19" 15 41] + [#t "2014-07-24" 13 18] + [#t "2015-09-18" 14 54] + [#t "2014-08-13" 11 84] + [#t "2014-03-29" 7 56] + [#t "2014-05-21" 13 37] + [#t "2014-04-30" 4 96] + [#t "2014-09-16" 6 76] + [#t "2014-07-08" 5 21] + [#t "2014-03-10" 8 61] + [#t "2014-09-05" 5 26] + [#t "2013-05-29" 8 100] + [#t "2014-05-08" 3 47] + [#t "2015-10-04" 7 46] + [#t "2014-02-10" 5 73] + [#t "2014-02-08" 1 54] + [#t "2014-06-29" 12 46] + [#t "2014-10-16" 14 46] + [#t "2015-10-29" 10 69] + [#t "2013-06-03" 1 39] + [#t "2014-03-09" 3 23] + [#t "2014-07-13" 10 43] + [#t "2014-04-17" 14 95] + [#t "2014-03-17" 10 75] + [#t "2013-02-18" 4 50] + [#t "2013-11-01" 12 43] + [#t "2015-07-02" 9 33] + [#t "2013-04-02" 4 91] + [#t "2014-04-12" 15 16] + [#t "2014-02-10" 3 42] + [#t "2014-03-20" 12 65] + [#t "2015-07-22" 13 72] + [#t "2015-05-01" 13 86] + [#t "2013-03-19" 13 93] + [#t "2013-12-19" 10 49] + [#t "2014-12-05" 13 8] + [#t "2015-08-09" 15 52] + [#t "2013-12-11" 7 95] + [#t "2014-10-10" 9 90] + [#t "2015-03-05" 8 50] + [#t "2014-01-12" 6 11] + [#t "2014-08-25" 13 26] + [#t "2014-10-14" 3 39] + [#t "2015-11-13" 8 36] + [#t "2014-05-20" 5 97] + [#t "2014-05-07" 10 35] + [#t "2015-04-06" 11 74] + [#t "2013-04-28" 15 75] + [#t "2014-01-18" 2 88] + [#t "2014-04-16" 9 58] + [#t "2014-11-05" 6 41] + [#t "2015-04-11" 10 44] + [#t "2013-07-20" 10 64] + [#t "2014-02-12" 10 19] + [#t "2014-03-01" 4 13] + [#t "2014-04-02" 13 27] + [#t "2013-03-28" 15 33] + [#t "2015-09-05" 3 6] + [#t "2014-03-08" 7 63] + [#t "2014-09-23" 12 94] + [#t "2014-04-03" 7 38] + [#t "2014-02-17" 11 85] + [#t "2014-07-13" 9 76] + [#t "2014-05-28" 8 83] + [#t "2015-02-03" 14 42] + [#t "2014-03-25" 4 35] + [#t "2014-03-25" 7 58] + [#t "2014-02-25" 3 54] + [#t "2014-12-16" 5 60] + [#t "2014-05-20" 9 100] + [#t "2014-04-09" 12 6] + [#t "2013-07-29" 3 76] + [#t "2013-04-26" 8 73] + [#t "2014-11-03" 13 33] + [#t "2014-05-17" 6 45] + [#t "2014-10-07" 5 87] + [#t "2015-07-21" 5 90] + [#t "2015-08-26" 9 36] + [#t "2015-05-21" 7 57] + [#t "2013-10-03" 9 20] + [#t "2013-05-18" 4 13] + [#t "2014-03-22" 13 63] + [#t "2015-08-22" 5 42] + [#t "2015-03-02" 9 49] + [#t "2014-10-02" 6 15] + [#t "2013-07-18" 7 17] + [#t "2014-02-17" 11 63] + [#t "2013-02-26" 3 90] + [#t "2013-06-08" 13 90] + [#t "2014-03-24" 6 46] + [#t "2014-02-19" 15 91] + [#t "2014-10-10" 10 65] + [#t "2014-05-08" 3 76] + [#t "2014-02-11" 13 43] + [#t "2015-07-09" 7 15] + [#t "2014-03-03" 1 36] + [#t "2014-08-03" 9 59] + [#t "2015-04-02" 5 86] + [#t "2014-07-11" 14 63] + [#t "2013-11-24" 5 94] + [#t "2014-08-03" 14 54] + [#t "2014-08-02" 2 37] + [#t "2015-11-08" 4 62] + [#t "2013-10-01" 7 17] + [#t "2014-01-03" 13 59] + [#t "2013-03-14" 4 22] + [#t "2015-12-16" 3 94] + [#t "2014-06-06" 14 89] + [#t "2015-08-19" 15 23] + [#t "2015-03-17" 8 12] + [#t "2015-05-29" 8 93] + [#t "2013-05-20" 3 20] + [#t "2013-04-03" 9 2] + [#t "2014-06-30" 13 73] + [#t "2015-05-04" 10 5] + [#t "2014-08-29" 4 98] + [#t "2015-04-15" 5 38] + [#t "2014-10-19" 3 41] + [#t "2013-06-21" 2 53] + [#t "2015-11-03" 12 97] + [#t "2013-05-06" 15 68] + [#t "2013-08-16" 15 22] + [#t "2013-05-01" 11 57] + [#t "2015-02-22" 6 91] + [#t "2014-04-22" 2 63] + [#t "2013-03-06" 13 70] + [#t "2013-11-02" 11 86] + [#t "2015-05-26" 13 23] + [#t "2013-11-17" 12 5] + [#t "2015-12-02" 5 43] + [#t "2014-03-26" 11 96] + [#t "2013-12-03" 2 90] + [#t "2014-08-24" 10 21] + [#t "2014-10-28" 11 20] + [#t "2015-09-11" 13 44] + [#t "2014-08-02" 11 57] + [#t "2014-09-29" 6 45] + [#t "2014-08-03" 13 68] + [#t "2015-07-24" 9 75] + [#t "2014-10-06" 7 37] + [#t "2014-11-06" 6 21] + [#t "2013-11-22" 14 49] + [#t "2014-08-03" 7 67] + [#t "2014-10-20" 4 75] + [#t "2015-08-26" 13 13] + [#t "2014-09-01" 2 58] + [#t "2013-03-19" 2 23] + [#t "2014-10-03" 4 38] + [#t "2015-10-06" 7 26] + [#t "2014-10-23" 2 93] + [#t "2014-07-02" 2 41] + [#t "2014-10-18" 7 99] + [#t "2014-09-10" 14 64] + [#t "2014-10-25" 10 9] + [#t "2014-12-25" 6 48] + [#t "2014-02-18" 8 58] + [#t "2014-08-25" 3 35] + [#t "2014-07-01" 6 98] + [#t "2013-09-16" 8 97] + [#t "2014-09-22" 13 26] + [#t "2014-04-15" 2 91] + [#t "2015-06-30" 6 20] + [#t "2014-06-13" 15 74] + [#t "2014-10-13" 7 62] + [#t "2014-06-03" 7 95] + [#t "2014-10-16" 1 96] + [#t "2013-10-20" 9 84] + [#t "2014-09-24" 4 55] + [#t "2014-02-16" 13 86] + [#t "2015-02-05" 14 9] + [#t "2014-06-19" 9 37] + [#t "2015-11-13" 3 12] + [#t "2014-06-10" 10 91] + [#t "2013-10-29" 1 13] + [#t "2013-05-10" 4 57] + [#t "2014-05-28" 5 57] + [#t "2013-06-18" 14 31] + [#t "2014-06-16" 3 29] + [#t "2015-06-07" 7 93] + [#t "2015-11-21" 7 87] + [#t "2015-09-26" 9 53] + [#t "2014-10-20" 14 93] + [#t "2014-08-14" 14 37] + [#t "2013-03-21" 3 30] + [#t "2013-06-05" 10 82] + [#t "2015-07-17" 4 40] + [#t "2014-09-08" 8 45] + [#t "2013-02-15" 6 84] + [#t "2015-11-06" 11 87] + [#t "2014-12-24" 10 93] + [#t "2014-08-02" 2 54] + [#t "2014-05-07" 3 34] + [#t "2014-10-01" 13 48] + [#t "2014-10-24" 4 48] + [#t "2015-04-25" 1 46] + [#t "2015-03-15" 14 85] + [#t "2014-03-05" 4 37] + [#t "2014-02-20" 6 62] + [#t "2014-08-20" 2 73] + [#t "2013-09-29" 2 14] + [#t "2013-09-01" 6 83] + [#t "2013-10-16" 11 89] + [#t "2013-12-04" 3 58] + [#t "2014-06-22" 3 36] + [#t "2015-06-26" 5 96] + [#t "2014-04-22" 5 18] + [#t "2014-10-29" 4 54] + [#t "2013-09-29" 9 31] + [#t "2015-04-19" 12 49] + [#t "2013-01-26" 3 38] + [#t "2013-01-03" 4 88] + [#t "2015-11-25" 12 58] + [#t "2015-08-24" 12 58] + [#t "2015-05-22" 15 3] + [#t "2013-05-04" 10 17] + [#t "2013-08-10" 6 85] + [#t "2015-07-09" 7 18] + [#t "2015-06-15" 12 67] + [#t "2015-02-22" 8 96] + [#t "2015-02-13" 15 88] + [#t "2015-12-22" 8 70] + [#t "2014-10-04" 8 48] + [#t "2013-06-05" 3 91] + [#t "2014-11-06" 8 83] + [#t "2013-11-28" 12 5] + [#t "2014-03-29" 13 88] + [#t "2014-11-02" 2 73] + [#t "2013-10-22" 7 13] + [#t "2015-06-16" 13 17] + [#t "2014-03-09" 7 11] + [#t "2014-03-06" 2 84] + [#t "2014-06-13" 8 79] + [#t "2014-04-10" 2 77] + [#t "2014-05-11" 3 40] + [#t "2013-03-06" 8 30] + [#t "2014-12-07" 1 47] + [#t "2014-12-21" 11 49] + [#t "2014-10-31" 5 39] + [#t "2014-10-22" 3 98] + [#t "2015-04-09" 9 20] + [#t "2013-07-23" 13 66] + [#t "2013-04-26" 15 18] + [#t "2013-02-06" 9 37] + [#t "2014-09-07" 12 79] + [#t "2014-04-26" 8 49] + [#t "2015-07-01" 6 87] + [#t "2015-09-27" 2 70] + [#t "2014-11-05" 7 44] + [#t "2014-11-27" 6 65] + [#t "2015-09-07" 8 51] + [#t "2015-08-21" 6 11] + [#t "2014-05-21" 11 76] + [#t "2014-09-20" 5 94] + [#t "2015-04-05" 1 97] + [#t "2014-11-21" 2 20] + [#t "2014-06-03" 9 25] + [#t "2013-09-21" 4 10] + [#t "2013-09-14" 14 78] + [#t "2014-05-30" 6 34] + [#t "2014-03-30" 1 16] + [#t "2014-09-23" 15 36] + [#t "2013-08-21" 8 5] + [#t "2014-10-10" 11 39] + [#t "2014-03-16" 4 66] + [#t "2014-10-07" 12 74] + [#t "2015-08-09" 6 76] + [#t "2015-07-22" 14 62] + [#t "2015-08-13" 14 98] + [#t "2014-04-03" 8 40] + [#t "2014-11-13" 3 33] + [#t "2014-05-09" 12 42] + [#t "2015-09-24" 8 77] + [#t "2014-12-09" 2 16] + [#t "2015-05-29" 4 29] + [#t "2014-03-05" 11 49] + [#t "2014-04-29" 13 58] + [#t "2014-05-04" 9 34] + [#t "2015-04-16" 12 5] + [#t "2015-02-07" 7 67] + [#t "2014-06-03" 2 92]]]] diff --git a/test/metabase/test/data/impl.clj b/test/metabase/test/data/impl.clj index 3c9147adeda05f3359cd1042092977d5816bc109..46383a1b63d4a02514d030e5a227edbf28870810 100644 --- a/test/metabase/test/data/impl.clj +++ b/test/metabase/test/data/impl.clj @@ -14,10 +14,17 @@ [metabase.test.data [dataset-definitions :as defs] [interface :as tx]] + [metabase.test.data.impl.verify :as verify] [metabase.test.initialize :as initialize] [metabase.test.util.timezone :as tu.tz] + [potemkin :as p] [toucan.db :as db])) +(comment verify/keep-me) + +(p/import-vars + [verify verify-data-loaded-correctly]) + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | get-or-create-database!; db | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -77,7 +84,7 @@ "Max amount of time to wait for sync to complete." (u/minutes->ms 5)) ; five minutes -(defn- create-database! [driver {:keys [database-name], :as database-definition}] +(defn- create-database! [driver {:keys [database-name table-definitions], :as database-definition}] {:pre [(seq database-name)]} (try ;; Create the database and load its data @@ -90,24 +97,34 @@ :name database-name :engine (name driver) :details (tx/dbdef->connection-details driver :db database-definition))] - ;; sync newly added DB - (u/with-timeout sync-timeout-ms - (u/profile (format "Sync %s Database %s" driver database-name) - (sync/sync-database! db) - ;; add extra metadata for fields - (try - (add-extra-metadata! database-definition db) - (catch Throwable e - (println "Error adding extra metadata:" e))))) - ;; make sure we're returing an up-to-date copy of the DB - (Database (u/get-id db))) + (try + ;; sync newly added DB + (u/with-timeout sync-timeout-ms + (u/profile (format "Sync %s Database %s" driver database-name) + (sync/sync-database! db) + (verify-data-loaded-correctly driver database-definition db) + ;; add extra metadata for fields + (try + (add-extra-metadata! database-definition db) + (catch Throwable e + (println "Error adding extra metadata:" e))))) + ;; make sure we're returing an up-to-date copy of the DB + (Database (u/get-id db)) + (catch Throwable e + (db/delete! Database :id (u/get-id db)) + (throw e)))) (catch Throwable e (printf "Failed to create %s '%s' test database:\n" driver database-name) (println e) - (when config/is-test? - (System/exit -1))))) - -(defmethod get-or-create-database! :default [driver dbdef] + (if config/is-test? + (System/exit -1) + (do + (println (u/format-color 'red "create-database! failed; destroying %s database %s" driver (pr-str database-name))) + (tx/destroy-db! driver database-definition) + (throw e)))))) + +(defmethod get-or-create-database! :default + [driver dbdef] (initialize/initialize-if-needed! :plugins :db) (let [dbdef (tx/get-dataset-definition dbdef)] (or @@ -119,8 +136,7 @@ ;; code may run inside of some other block that sets report timezone ;; ;; require/resolve used here to avoid circular refs - (require 'metabase.test.util) - ((resolve 'metabase.test.util/do-with-temporary-setting-value) + ((requiring-resolve 'metabase.test.util/do-with-temporary-setting-value) :report-timezone nil #(create-database! driver dbdef))))))) @@ -150,18 +166,18 @@ "Internal impl of `(data/id table)." [db-id table-name] {:pre [(integer? db-id) ((some-fn keyword? string?) table-name)]} - (let [table-id-for-name (partial db/select-one-id Table, :db_id db-id, :name)] + (let [table-name (name table-name) + table-id-for-name (partial db/select-one-id Table, :db_id db-id, :name)] (or (table-id-for-name table-name) (table-id-for-name (let [db-name (db/select-one-field :name Database :id db-id)] (tx/db-qualified-table-name db-name table-name))) (let [{driver :engine, db-name :name} (db/select-one [Database :engine :name] :id db-id)] (throw - (Exception. (format "No Table '%s' found for %s Database %d '%s'.\nFound: %s" - table-name driver db-id db-name + (Exception. (format "No Table %s found for %s Database %d %s.\nFound: %s" + (pr-str table-name) driver db-id (pr-str db-name) (u/pprint-to-str (db/select-id->field :name Table, :db_id db-id, :active true))))))))) (defn- the-field-id* [table-id field-name & {:keys [parent-id]}] - {:pre [((some-fn keyword? string?) field-name)]} (or (db/select-one-id Field, :active true, :table_id table-id, :name field-name, :parent_id parent-id) (let [{db-id :db_id, table-name :name} (db/select-one [Table :name :db_id] :id table-id) {driver :engine, db-name :name} (db/select-one [Database :engine :name] :id db-id) @@ -176,6 +192,11 @@ "Internal impl of `(data/id table field)`." [table-id field-name & nested-field-names] {:pre [(integer? table-id)]} + (doseq [field-name (cons field-name nested-field-names)] + (assert ((some-fn keyword? string?) field-name) + (format "Expected keyword or string field name; got ^%s %s" + (some-> field-name class .getCanonicalName) + (pr-str field-name)))) (loop [parent-id (the-field-id* table-id field-name), [nested-field-name & more] nested-field-names] (if-not nested-field-name parent-id diff --git a/test/metabase/test/data/impl/verify.clj b/test/metabase/test/data/impl/verify.clj new file mode 100644 index 0000000000000000000000000000000000000000..298ce8c23c31e5116efb8f1fad57f1cf89a13edb --- /dev/null +++ b/test/metabase/test/data/impl/verify.clj @@ -0,0 +1,125 @@ +(ns metabase.test.data.impl.verify + "Logic for verifying that test data was loaded correctly." + (:require [clojure.tools.logging :as log] + [metabase + [driver :as driver] + [models :refer [Table]] + [query-processor :as qp] + [util :as u]] + [metabase.test.data.interface :as tx] + [toucan.db :as db])) + +(defmulti verify-data-loaded-correctly + "Make sure a `DatabaseDefinition` (see `metabase.test.data.interface`) was loaded correctly. This checks that all + defined Tables and Fields exist and the correct number of rows exist." + {:arglists '([driver database-definition database])} + tx/dispatch-on-driver-with-test-extensions + :hierarchy #'driver/hierarchy) + +(defn- loaded-tables + "Actual Tables loaded into `database`. Returns a set of `[schema-name table-name]` pairs." + [{:keys [driver database]}] + (->> (driver/describe-database driver database) + :tables + (map (juxt :schema :name)) + set)) + +(defn- loaded-fields + "Actual Fields loaded into a Table. Returns set of field names." + [{:keys [driver database actual-schema actual-table-name]}] + (->> (driver/describe-table driver database {:schema actual-schema, :name actual-table-name}) + :fields + (map :name) + set)) + +(defn- format-name + "Format an identifier e.g. Database or Table name for `driver`." + [driver a-name] + (binding [driver/*driver* driver] + ((requiring-resolve 'metabase.test.data/format-name) a-name))) + +(defn- params->ex-data + "Remove the actual definitions from `params` so Exceptions aren't too noisy." + [{:keys [database-definition table-definition field-definition], :as params}] + (-> params + (dissoc :database-definition :table-definition :field-definition) + (assoc :original-database-name (:database-name database-definition) + :original-table-name (:table-name table-definition) + :original-field-name (:field-name field-definition)))) + +(defn- verify-field + "Make sure the Field defined by a FieldDefinition exists." + [{:keys [driver actual-table-name actual-field-names] + {:keys [field-name]} :field-definition + :as params}] + (let [field-name (format-name driver field-name)] + (log/debugf "Checking whether Field %s.%s was loaded correctly..." (pr-str actual-table-name) (pr-str field-name)) + (when-not (contains? actual-field-names field-name) + (throw (ex-info (format "Error loading data: Field %s.%s does not exist after sync" + (pr-str actual-table-name) (pr-str field-name)) + (params->ex-data params)))) + (log/debugf "Found Field %s.%s" (pr-str actual-table-name) (pr-str field-name)))) + +(defn- verify-table + "Make sure the Table defined by a TableDefinition was loaded correctly (check that it exist and all the correct Fields + exist)." + [{:keys [driver database actual-tables] + {:keys [table-name field-definitions rows]} :table-definition + :as params}] + (log/debugf "Checking whether Table %s was loaded correctly..." (pr-str table-name)) + (let [table-name (format-name driver table-name) + qualified-table-name (tx/db-qualified-table-name (:name database) table-name) + [actual-schema actual-name] (or (some (fn [[_ a-name :as schema+table]] + (when (or (= a-name table-name) + (= a-name qualified-table-name)) + schema+table)) + actual-tables) + (throw (ex-info (format "Error loading data: Table %s does not exist after sync" + (pr-str table-name)) + (params->ex-data params))))] + (log/debugf "Found Table %s.%s" (pr-str actual-schema) (pr-str actual-name)) + (let [params (assoc params :actual-schema actual-schema, :actual-table-name actual-name) + params (assoc params :actual-field-names (loaded-fields params))] + (log/debugf "Verifying fields...") + (doseq [fielddef field-definitions + :let [params (assoc params :field-definition fielddef)]] + (try + (verify-field params) + (catch Throwable e + (throw (ex-info "Error verifying Field." (params->ex-data params) e))))) + (log/debugf "All Fields for Table %s.%s loaded correctly." (pr-str actual-schema) (pr-str actual-name)) + (log/debugf "Verifying rows...") + (let [table-id (or (db/select-one-id Table :db_id (u/get-id database), :name actual-name) + (throw (ex-info (format "Cannot find %s.%s after sync." (pr-str actual-schema) (pr-str actual-name)) + (params->ex-data params)))) + expected-row-count (count rows) + actual-row-count (-> (qp/process-query {:database (u/get-id database) + :type :query + :query {:source-table table-id + :aggregation [[:count]]}}) + :data + :rows + ffirst + int)] + (log/debugf "Expected rows: %d. Actual rows: %d" expected-row-count actual-row-count) + (when-not (= expected-row-count actual-row-count) + (throw (ex-info (format "Incorrect number of rows loaded for Table %s.%s. Expected: %d. Actual: %d" + (pr-str actual-schema) (pr-str actual-name) + expected-row-count actual-row-count) + (params->ex-data params)))) + (log/debugf "Table %s.%s loaded correctly." (pr-str actual-schema) (pr-str actual-name)))))) + +(defmethod verify-data-loaded-correctly :default + [driver {:keys [table-definitions database-name], :as dbdef} database] + (log/debugf "Verifying data for Database %s loaded correctly..." (pr-str database-name)) + (let [params {:driver driver + :database-definition dbdef + :database database} + actual-tables (loaded-tables params)] + (doseq [tabledef table-definitions + :let [params (assoc params :actual-tables actual-tables, :table-definition tabledef)]] + (try + (verify-table params) + (catch Throwable e + (throw (ex-info "Error verifying Table." (params->ex-data params) e)))))) + (log/debugf "All Tables for Database %s loaded correctly." (pr-str database-name))) diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj index b12c5e89def393f51ff90ccc69ff9f55f479fd0c..befcd0aaef857ef9f9d8982decc79e971a3fe30d 100644 --- a/test/metabase/test/data/interface.clj +++ b/test/metabase/test/data/interface.clj @@ -273,6 +273,14 @@ dispatch-on-driver-with-test-extensions :hierarchy #'driver/hierarchy) +(defmulti destroy-db! + "Destroy the database created for `database-definition`, if one exists. This is only called if loading data fails for + one reason or another, to revert the changes made thus far; implementations should clean up everything related to + the database in question." + {:arglists '([driver database-definition])} + dispatch-on-driver-with-test-extensions + :hierarchy #'driver/hierarchy) + (defmulti format-name "Transform a lowercase string Table or Field name in a way appropriate for this dataset (e.g., `h2` would want to diff --git a/test/metabase/test/data/postgres.clj b/test/metabase/test/data/postgres.clj index cf57e6abf34912270083ec8f356b488e68f06b3f..5e923c9c4bbf2e0ba6fc7a094b5595b0573dd8b1 100644 --- a/test/metabase/test/data/postgres.clj +++ b/test/metabase/test/data/postgres.clj @@ -64,8 +64,12 @@ "END $$;\n") (name database-name))) -(defmethod ddl/drop-db-ddl-statements :postgres [driver {:keys [database-name], :as dbdef} & options] - ;; add an additonal statement to the front to kill open connections to the DB before dropping +(defmethod ddl/drop-db-ddl-statements :postgres + [driver {:keys [database-name], :as dbdef} & options] + (assert (string? database-name) + (format "Expected String database name; got ^%s %s" + (some-> database-name class .getCanonicalName) (pr-str database-name))) + ;; add an additional statement to the front to kill open connections to the DB before dropping (cons (kill-connections-to-db-sql database-name) (apply (get-method ddl/drop-db-ddl-statements :sql-jdbc/test-extensions) :postgres dbdef options))) diff --git a/test/metabase/test/data/sql.clj b/test/metabase/test/data/sql.clj index 7448a93cff4a723aa53d80f071aea0e0d6f05509..4c4045c23e3710cdb6c03b9da7cddb4e2c019252 100644 --- a/test/metabase/test/data/sql.clj +++ b/test/metabase/test/data/sql.clj @@ -204,8 +204,9 @@ [driver {:keys [database-name], :as dbdef} {:keys [table-name field-definitions table-comment]}] (let [quot #(sql.u/quote-name driver :field (tx/format-name driver %)) pk-field-name (quot (pk-field-name driver))] - (format "CREATE TABLE %s (%s, %s %s, PRIMARY KEY (%s)) %s;" + (format "CREATE TABLE %s (%s %s, %s, PRIMARY KEY (%s)) %s;" (qualify-and-quote driver database-name table-name) + pk-field-name (pk-sql-type driver) (str/join ", " (for [{:keys [field-name base-type field-comment]} field-definitions] @@ -216,7 +217,6 @@ (field-base-type->sql-type driver base-type))) (when-let [comment (inline-column-comment-sql driver field-comment)] (str " " comment))))) - pk-field-name (pk-sql-type driver) pk-field-name (or (inline-table-comment-sql driver table-comment) "")))) @@ -259,13 +259,16 @@ (defmethod tx/count-with-template-tag-query :sql/test-extensions [driver table field param-type] + ;; generate a SQL query like SELECT count(*) ... WHERE last_login = 1 + ;; then replace 1 with a template tag like {{last_login}} (driver/with-driver driver (let [mbql-query (data/mbql-query nil {:source-table (data/id table) :aggregation [[:count]] :filter [:= [:field-id (data/id table field)] 1]}) {:keys [query]} (qp/query->native mbql-query) - query (str/replace query (re-pattern #"= .*") (format "= {{%s}}" (name field)))] + ;; preserve stuff like cast(1 AS datetime) in the resulting query + query (str/replace query (re-pattern #"= (.*)(?:1)(.*)") (format "= $1{{%s}}$2" (name field)))] {:query query}))) (defmethod tx/count-with-field-filter-query :sql/test-extensions diff --git a/test/metabase/test/data/sql_jdbc.clj b/test/metabase/test/data/sql_jdbc.clj index 37fccfe76eafd14adea8ecbb292852e0f64eae39..7b494b26c802a13dd780e2fd6ff9313e6c31a137 100644 --- a/test/metabase/test/data/sql_jdbc.clj +++ b/test/metabase/test/data/sql_jdbc.clj @@ -16,18 +16,10 @@ (driver/add-parent! driver :sql-jdbc/test-extensions) (println "Added SQL JDBC test extensions for" driver "âž•")) -(defmethod tx/create-db! :sql-jdbc/test-extensions [& args] +(defmethod tx/create-db! :sql-jdbc/test-extensions + [& args] (apply load-data/create-db! args)) -(defmethod tx/aggregate-column-info :sqlserver - ([driver ag-type] - (merge - ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type) - (when (#{:count :cum-count} ag-type) - {:base_type :type/Integer}))) - - ([driver ag-type field] - (merge - ((get-method tx/aggregate-column-info ::tx/test-extensions) driver ag-type field) - (when (#{:count :cum-count} ag-type) - {:base_type :type/Integer})))) +(defmethod tx/destroy-db! :sql-jdbc/test-extensions + [driver dbdef] + (load-data/destroy-db! driver dbdef)) diff --git a/test/metabase/test/data/sql_jdbc/execute.clj b/test/metabase/test/data/sql_jdbc/execute.clj index c21fabe30e58ad7c911979be388342da0c9de516..5fd4b9efd51f43f15953f0b7da18c1a71a34b4ae 100644 --- a/test/metabase/test/data/sql_jdbc/execute.clj +++ b/test/metabase/test/data/sql_jdbc/execute.clj @@ -1,6 +1,7 @@ (ns metabase.test.data.sql-jdbc.execute (:require [clojure.java.jdbc :as jdbc] [clojure.string :as str] + [clojure.tools.logging :as log] [metabase.driver :as driver] [metabase.test.data.interface :as tx] [metabase.test.data.sql-jdbc.spec :as spec]) @@ -11,6 +12,7 @@ ;;; +----------------------------------------------------------------------------------------------------------------+ (defn- jdbc-execute! [db-spec sql] + (log/tracef "[execute] %s" (pr-str sql)) (jdbc/execute! db-spec [sql] {:transaction? false, :multi? true})) (defn default-execute-sql! [driver context dbdef sql & {:keys [execute!] diff --git a/test/metabase/test/data/sql_jdbc/load_data.clj b/test/metabase/test/data/sql_jdbc/load_data.clj index 9d2e242985fa2dbb4b7c3fbe70b2100589006cdc..c289ba20f496bfa88b5147cc65ace4f3a04609ba 100644 --- a/test/metabase/test/data/sql_jdbc/load_data.clj +++ b/test/metabase/test/data/sql_jdbc/load_data.clj @@ -68,7 +68,7 @@ specified. (This isn't meant for composition with `load-data-get-rows`; " [rows] (for [[i row] (m/indexed rows)] - (assoc row :id (inc i)))) + (into {:id (inc i)} row))) (defn load-data-add-ids "Middleware function intended for use with `make-load-data-fn`. Add IDs to each row, presumabily for doing a parallel @@ -186,6 +186,7 @@ (try ;; TODO - why don't we use `execute/execute-sql!` here like we do below? (doseq [sql+args statements] + (log/tracef "[insert] %s" (pr-str sql+args)) (jdbc/execute! spec sql+args {:set-parameters (fn [stmt params] (sql-jdbc.execute/set-parameters! driver stmt params))})) (catch SQLException e @@ -208,3 +209,9 @@ (doseq [tabledef table-definitions] (u/profile (format "load-data for %s %s %s" (name driver) (:database-name dbdef) (:table-name tabledef)) (load-data! driver dbdef tabledef)))) + +(defn destroy-db! + "Default impl of `destroy-db!` for SQL drivers." + [driver dbdef] + (doseq [statement (apply ddl/drop-db-ddl-statements driver dbdef)] + (execute/execute-sql! driver :server dbdef statement))) diff --git a/test/metabase/test/mock/moviedb.clj b/test/metabase/test/mock/moviedb.clj index 854975e1081a3c48f6c32a5c4532af5994a6d7ce..6dc96fbaea8b6ffbc5ea11e79c09a3a4a996244b 100644 --- a/test/metabase/test/mock/moviedb.clj +++ b/test/metabase/test/mock/moviedb.clj @@ -79,7 +79,9 @@ (defmethod driver/describe-table ::moviedb [_ _ table] (-> (get moviedb-tables (:name table)) - (dissoc :fks))) + (dissoc :fks) + (update :fields (partial map-indexed (fn [idx field] + (assoc field :database-position idx)))))) (defmethod driver/describe-table-fks ::moviedb [_ _ table] (-> (get moviedb-tables (:name table)) diff --git a/test/metabase/test/mock/toucanery.clj b/test/metabase/test/mock/toucanery.clj index e0fbaf34ff2b6b4e9e6c7135f0927f40e4aa38d9..168bba658b9f117bbcc9f9607d28e02b163e15c2 100644 --- a/test/metabase/test/mock/toucanery.clj +++ b/test/metabase/test/mock/toucanery.clj @@ -1,7 +1,8 @@ (ns metabase.test.mock.toucanery "A document style database mocked for testing. This is a dynamic schema db with `:nested-fields`. Most notably meant to serve as a representation of a Mongo database." - (:require [metabase.driver :as driver] + (:require [medley.core :as m] + [metabase.driver :as driver] [metabase.test.mock.util :as mock-util])) (def toucanery-tables @@ -58,9 +59,17 @@ (select-keys table [:schema :name]))] {:tables (set tables)})) +(defn- add-db-position + [field position] + (-> field + (assoc :database-position position) + (m/update-existing :nested-fields (partial (comp set map) #(add-db-position % position))))) + (defmethod driver/describe-table ::toucanery [_ _ table] - (get toucanery-tables (:name table))) + (-> (get toucanery-tables (:name table)) + (update :fields (partial (comp set map-indexed) (fn [idx field] + (add-db-position field idx)))))) (defmethod driver/table-rows-seq ::toucanery [_ _ table] @@ -84,26 +93,31 @@ [(merge mock-util/table-defaults {:name "employees" :fields [(merge mock-util/field-defaults - {:name "id" - :display_name "ID" - :database_type "SERIAL" - :base_type :type/Integer - :special_type :type/PK}) - (merge mock-util/field-defaults {:name "name" :display_name "Name" :database_type "VARCHAR" :base_type :type/Text - :special_type :type/Name})] + :special_type :type/Name}) + (merge mock-util/field-defaults + {:name "id" + :display_name "ID" + :database_type "SERIAL" + :base_type :type/Integer + :special_type :type/PK})] :display_name "Employees"}) (merge mock-util/table-defaults {:name "transactions" :fields [(merge mock-util/field-defaults - {:name "age" - :display_name "Age" - :database_type "INT" - :base_type :type/Integer - :parent_id true}) + {:name "ts" + :display_name "Ts" + :database_type "BIGINT" + :base_type :type/BigInteger + :special_type :type/UNIXTimestampMilliseconds}) + (merge mock-util/field-defaults + {:name "id" + :display_name "ID" + :database_type "SERIAL" + :base_type :type/Integer}) (merge mock-util/field-defaults {:name "buyer" :display_name "Buyer" @@ -115,18 +129,6 @@ :database_type "VARCHAR" :base_type :type/Text :parent_id true}) - (merge mock-util/field-defaults - {:name "details" - :display_name "Details" - :database_type "OBJECT" - :base_type :type/Dictionary - :parent_id true}) - (merge mock-util/field-defaults - {:name "id" - :display_name "ID" - :database_type "SERIAL" - :base_type :type/Integer - :special_type :type/PK}) (merge mock-util/field-defaults {:name "name" :display_name "Name" @@ -134,6 +136,18 @@ :base_type :type/Text :parent_id true :special_type :type/Name}) + (merge mock-util/field-defaults + {:name "age" + :display_name "Age" + :database_type "INT" + :base_type :type/Integer + :parent_id true}) + (merge mock-util/field-defaults + {:name "details" + :display_name "Details" + :database_type "OBJECT" + :base_type :type/Dictionary + :parent_id true}) (merge mock-util/field-defaults {:name "name" :display_name "Name" @@ -146,12 +160,6 @@ :display_name "Toucan" :database_type "OBJECT" :base_type :type/Dictionary}) - (merge mock-util/field-defaults - {:name "ts" - :display_name "Ts" - :database_type "BIGINT" - :base_type :type/BigInteger - :special_type :type/UNIXTimestampMilliseconds}) (merge mock-util/field-defaults {:name "weight" :display_name "Weight" diff --git a/test/metabase/test/util_test.clj b/test/metabase/test/util_test.clj index cfea727753e03ea932e1b849b391ecc005bdb632..ff9e7faf163fc427ba81183ecf7c91461ea4baf8 100644 --- a/test/metabase/test/util_test.clj +++ b/test/metabase/test/util_test.clj @@ -1,31 +1,30 @@ (ns metabase.test.util-test "Tests for the test utils!" - (:require [expectations :refer [expect]] + (:require [clojure.test :refer :all] + [metabase + [test :as mt] + [util :as u]] [metabase.models [field :refer [Field]] [setting :as setting]] - [metabase.test - [data :as data] - [util :as tu]] - [metabase.util :as u] + [metabase.test.data :as data] [toucan.db :as db])) -;; let's make sure this acutally works right! -(expect - [-1 0] - (let [position #(db/select-one-field :position Field :id (data/id :venues :price))] - [(tu/with-temp-vals-in-db Field (data/id :venues :price) {:position -1} - (position)) - (position)])) +(deftest with-temp-vals-in-db-test + (testing "let's make sure this acutally works right!" + (let [position #(db/select-one-field :position Field :id (data/id :venues :price))] + (mt/with-temp-vals-in-db Field (data/id :venues :price) {:position -1} + (is (= -1 + (position)))) + (is (= 5 + (position))))) -(expect - 0 - (do + (testing "if an Exception is thrown, original value should be restored" (u/ignore-exceptions - (tu/with-temp-vals-in-db Field (data/id :venues :price) {:position -1} - (throw (Exception.)))) - (db/select-one-field :position Field :id (data/id :venues :price)))) - + (mt/with-temp-vals-in-db Field (data/id :venues :price) {:position -1} + (throw (Exception.)))) + (is (= 5 + (db/select-one-field :position Field :id (data/id :venues :price)))))) (setting/defsetting test-util-test-setting "Another internal test setting" @@ -33,16 +32,14 @@ :default "A,B,C" :type :csv) -;; `with-temporary-setting-values` should do its thing -(expect - ["D" "E" "F"] - (tu/with-temporary-setting-values [test-util-test-setting ["D" "E" "F"]] - (test-util-test-setting))) +(deftest with-temporary-setting-values-test + (testing "`with-temporary-setting-values` should do its thing" + (mt/with-temporary-setting-values [test-util-test-setting ["D" "E" "F"]] + (is (= ["D" "E" "F"] + (test-util-test-setting))))) -;; `with-temporary-setting-values` shouldn't stomp over default values -(expect - ["A" "B" "C"] - (do - (tu/with-temporary-setting-values [test-util-test-setting ["D" "E" "F"]] + (testing "`with-temporary-setting-values` shouldn't stomp over default values" + (mt/with-temporary-setting-values [test-util-test-setting ["D" "E" "F"]] (test-util-test-setting)) - (test-util-test-setting))) + (is (= ["A" "B" "C"] + (test-util-test-setting))))) diff --git a/test/metabase/timeseries_query_processor_test.clj b/test/metabase/timeseries_query_processor_test.clj index 1c34ef0aef7e91f7f463b760435ad5907706b125..b708900318febfcbba0d4f6eb13c7d0e5129d07f 100644 --- a/test/metabase/timeseries_query_processor_test.clj +++ b/test/metabase/timeseries_query_processor_test.clj @@ -9,21 +9,24 @@ (deftest limit-test (tqp.test/test-timeseries-drivers - (is (= {:columns ["id" - "count" - "timestamp" - "user_last_login" - "user_name" - "venue_category_name" - "venue_latitude" - "venue_longitude" - "venue_name" - "venue_price"] - :rows [["931" 1 "2013-01-03T08:00:00Z" "2014-01-01T08:30:00.000Z" "Simcha Yan" "Thai" "34.094" "-118.344" "Kinaree Thai Bistro" "1"] - ["285" 1 "2013-01-10T08:00:00Z" "2014-07-03T01:30:00.000Z" "Kfir Caj" "Thai" "34.1021" "-118.306" "Ruen Pair Thai Restaurant" "2"]]} - (mt/rows+column-names - (mt/run-mbql-query checkins - {:limit 2})))))) + (is (= {:columns + ["timestamp" + "venue_name" + "venue_longitude" + "venue_latitude" + "venue_price" + "venue_category_name" + "id" + "count" + "user_name" + "user_last_login"] + + :rows + [["2013-01-03T08:00:00Z" "Kinaree Thai Bistro" "-118.344" "34.094" "1" "Thai" "931" 1 "Simcha Yan" "2014-01-01T08:30:00.000Z"] + ["2013-01-10T08:00:00Z" "Ruen Pair Thai Restaurant" "-118.306" "34.1021" "2" "Thai" "285" 1 "Kfir Caj" "2014-07-03T01:30:00.000Z"]]} + (mt/rows+column-names + (mt/run-mbql-query checkins + {:limit 2})))))) (deftest fields-test (tqp.test/test-timeseries-drivers @@ -35,26 +38,27 @@ {:fields [$venue_name $venue_category_name $timestamp] :limit 2})))))) -;; TODO -- `:desc` tests are disabled for now, they don't seem to be working on Druid 0.11.0. Enable once we merge PR to use Druid 0.17.0 +;; TODO -- `:desc` tests are disabled for now, they don't seem to be working on Druid 0.11.0. Enable once we merge PR +;; to use Druid 0.17.0 (deftest order-by-timestamp-test (tqp.test/test-timeseries-drivers (testing "query w/o :fields" (doseq [[direction expected-rows] {#_:desc #_[["693" 1 "2015-12-29T08:00:00Z" "2014-07-03T19:30:00.000Z" "Frans Hevel" "Mexican" "34.0489" "-118.238" "Señor Fish" "2"] ["570" 1 "2015-12-26T08:00:00Z" "2014-07-03T01:30:00.000Z" "Kfir Caj" "Chinese" "37.7949" "-122.406" "Empress of China" "3"]] - :asc [["931" 1 "2013-01-03T08:00:00Z" "2014-01-01T08:30:00.000Z" "Simcha Yan" "Thai" "34.094" "-118.344" "Kinaree Thai Bistro" "1"] - ["285" 1 "2013-01-10T08:00:00Z" "2014-07-03T01:30:00.000Z" "Kfir Caj" "Thai" "34.1021" "-118.306" "Ruen Pair Thai Restaurant" "2"]]}] + :asc [["2013-01-03T08:00:00Z" "Kinaree Thai Bistro" "-118.344" "34.094" "1" "Thai" "931" 1 "Simcha Yan" "2014-01-01T08:30:00.000Z"] + ["2013-01-10T08:00:00Z" "Ruen Pair Thai Restaurant" "-118.306" "34.1021" "2" "Thai" "285" 1 "Kfir Caj" "2014-07-03T01:30:00.000Z"]]}] (testing direction - (is (= {:columns ["id" + (is (= {:columns ["timestamp" + "venue_name" + "venue_longitude" + "venue_latitude" + "venue_price" + "venue_category_name" + "id" "count" - "timestamp" - "user_last_login" "user_name" - "venue_category_name" - "venue_latitude" - "venue_longitude" - "venue_name" - "venue_price"] + "user_last_login"] :rows expected-rows} (mt/rows+column-names (mt/run-mbql-query checkins diff --git a/test/metabase/transforms/core_test.clj b/test/metabase/transforms/core_test.clj index 5631c86e29d3568da2d43bc0a9e34c303fe2f67d..5eed5edfd1b39376cffb95ccc079eb4e12d835a6 100644 --- a/test/metabase/transforms/core_test.clj +++ b/test/metabase/transforms/core_test.clj @@ -1,8 +1,10 @@ (ns metabase.transforms.core-test - (:require [expectations :refer [expect]] + (:require [clojure.test :refer :all] + [expectations :refer [expect]] [medley.core :as m] [metabase [query-processor :as qp] + [test :as mt] [util :as u]] [metabase.domain-entities [core :as de] @@ -12,21 +14,17 @@ [collection :refer [Collection]] [table :as table :refer [Table]]] [metabase.test - [data :as data] [domain-entities :refer :all] - [transforms :refer :all] - [util :as tu]] - [metabase.test.data.users :as test-users] + [transforms :refer :all]] [metabase.transforms [core :as t] [specs :as t.specs]] - [toucan.db :as db] - [toucan.util.test :as tt])) + [toucan.db :as db])) (def test-bindings (delay (with-test-domain-entity-specs - (let [table (m/find-first (comp #{(data/id :venues)} u/get-id) (#'t/tableset (data/id) "PUBLIC"))] + (let [table (m/find-first (comp #{(mt/id :venues)} u/get-id) (#'t/tableset (mt/id) "PUBLIC"))] {"Venues" {:dimensions (m/map-vals de/mbql-reference (get-in table [:domain_entity :dimensions])) :entity table}})))) @@ -45,7 +43,7 @@ (expect "PRICE" - (#'t/mbql-reference->col-name [:field-id (data/id :venues :price)])) + (#'t/mbql-reference->col-name [:field-id (mt/id :venues :price)])) (expect "PRICE" @@ -53,30 +51,31 @@ (expect "PRICE" - (#'t/mbql-reference->col-name [{:foo [:field-id (data/id :venues :price)]}])) + (#'t/mbql-reference->col-name [{:foo [:field-id (mt/id :venues :price)]}])) +(deftest ->source-table-reference-test + (testing "Can we turn a given entity into a format suitable for a query's `:source_table`?" + (testing "for a Table" + (is (= (mt/id :venues) + (#'t/->source-table-reference (Table (mt/id :venues)))))) -;; Can we turn a given entity into a format suitable for a query's `:source_table`? -(expect - (data/id :venues) - (#'t/->source-table-reference (Table (data/id :venues)))) - -(tt/expect-with-temp [Card [{card-id :id}]] - (str "card__" card-id) - (#'t/->source-table-reference (Card card-id))) + (testing "for a Card" + (mt/with-temp Card [{card-id :id}] + (is (= (str "card__" card-id) + (#'t/->source-table-reference (Card card-id)))))))) ;; Can we get a tableset for a given schema? (expect - (db/select-ids Table :db_id (data/id)) - (set (map u/get-id (#'t/tableset (data/id) "PUBLIC")))) + (db/select-ids Table :db_id (mt/id)) + (set (map u/get-id (#'t/tableset (mt/id) "PUBLIC")))) ;; Can we filter a tableset by domain entity? (expect - [(data/id :venues)] + [(mt/id :venues)] (with-test-domain-entity-specs - (map u/get-id (#'t/find-tables-with-domain-entity (#'t/tableset (data/id) "PUBLIC") + (map u/get-id (#'t/find-tables-with-domain-entity (#'t/tableset (mt/id) "PUBLIC") (@de.specs/domain-entity-specs "Venues"))))) ;; Greacefully handle no-match @@ -89,19 +88,19 @@ ;; Can we extract results from the final bindings? (expect - [(data/id :venues)] + [(mt/id :venues)] (with-test-transform-specs - (map u/get-id (#'t/resulting-entities {"VenuesEnhanced" {:entity (Table (data/id :venues)) + (map u/get-id (#'t/resulting-entities {"VenuesEnhanced" {:entity (Table (mt/id :venues)) :dimensions {"D1" [:field-id 1]}}} (first @t.specs/transform-specs))))) ;; Can we find a table set matching requirements of a given spec? (expect - [(data/id :venues)] + [(mt/id :venues)] (with-test-transform-specs (with-test-domain-entity-specs - (map u/get-id (#'t/tables-matching-requirements (#'t/tableset (data/id) "PUBLIC") + (map u/get-id (#'t/tables-matching-requirements (#'t/tableset (mt/id) "PUBLIC") (first @t.specs/transform-specs)))))) @@ -109,7 +108,7 @@ (expect @test-bindings (with-test-domain-entity-specs - (#'t/tableset->bindings (filter (comp #{(data/id :venues)} u/get-id) (#'t/tableset (data/id) "PUBLIC"))))) + (#'t/tableset->bindings (filter (comp #{(mt/id :venues)} u/get-id) (#'t/tableset (mt/id) "PUBLIC"))))) ;; Is the validation of results working? @@ -128,32 +127,31 @@ java.lang.AssertionError (with-test-domain-entity-specs (with-test-transform-specs - (#'t/validate-results {"VenuesEnhanced" {:entity (Table (data/id :venues)) + (#'t/validate-results {"VenuesEnhanced" {:entity (Table (mt/id :venues)) :dimensions {"D1" [:field-id 1]}}} (first @t.specs/transform-specs))))) -;; Run the transform and make sure it produces the correct result -(expect - [[4 1 10.0646 -165.374 "Red Medicine" 3 1.5 4 3 2 1] - [11 2 34.0996 -118.329 "Stout Burgers & Beers" 2 2.0 11 2 1 1] - [11 3 34.0406 -118.428 "The Apple Pan" 2 2.0 11 2 1 1]] - (test-users/with-test-user :rasta - (with-test-domain-entity-specs - (tu/with-model-cleanup [Card Collection] - (-> (t/apply-transform! (data/id) "PUBLIC" test-transform-spec) - first - :dataset_query - qp/process-query - :data - :rows))))) - - -;; Can we find the right transform(s) for a given table -(expect - "Test transform" - (with-test-transform-specs - (with-test-domain-entity-specs - (-> (t/candidates (Table (data/id :venues))) - first - :name)))) +(deftest transform-test + (testing "Run the transform and make sure it produces the correct result" + (mt/with-test-user :rasta + (with-test-domain-entity-specs + (mt/with-model-cleanup [Card Collection] + (is (= [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5 4 3 2 1] + [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0 11 2 1 1] + [3 "The Apple Pan" 11 34.0406 -118.428 2 2.0 11 2 1 1]] + (-> (t/apply-transform! (mt/id) "PUBLIC" test-transform-spec) + first + :dataset_query + qp/process-query + :data + :rows)))))))) + +(deftest correct-transforms-for-table-test + (testing "Can we find the right transform(s) for a given table" + (with-test-transform-specs + (with-test-domain-entity-specs + (is (= "Test transform" + (-> (t/candidates (Table (mt/id :venues))) + first + :name)))))))