class ColumnsTask(Task):
    '''
    The ColumnsTask provides a structure for generating metadata. The only
    required method is :meth:`~.ColumnsTask.columns`.
    The keys may be used as human-readable column names in tables based off
    this metadata, although that is not always the case. If the id of the
    :class:`OBSColumn <tasks.meta.OBSColumn>` is left blank, the dict's key will be used to
    generate it (qualified by the module).

    Also, conventionally there will be a requires method that brings in our
    standard tags: :class:`SectionTags <tasks.tags.SectionTags>`,
    :class:`SubsectionTags <tasks.tags.SubsectionTags>`, and
    :class:`UnitTags <tasks.tags.UnitTags>`.
    This is an example of defining several tasks as prerequisites: the outputs
    of those tasks will be accessible via ``self.input()[<key>]`` in other
    methods.

    This will update-or-create columns defined in it when run.  Whether a
    column is updated or not depends on whether it exists in the database with
    the same :meth:`~.ColumnsTask.version` number.
    '''

    def columns(self):
        '''
        This method must be overriden in subclasses.  It must return a
        :py:class:`collections.OrderedDict` whose values are all instances of
        :class:`OBSColumn <tasks.meta.OBSColumn>` and whose keys are all strings.
        '''
        raise NotImplementedError('Must return iterable of OBSColumns')

    def on_failure(self, ex):
        session_rollback(self, ex)
        super(ColumnsTask, self).on_failure(ex)

    def on_success(self):
        session_commit(self)

    def run(self):
        for _, coltarget in self.output().items():
            coltarget.update_or_create()

    def version(self):
        '''
        Returns a number that will be linked to the all
        :py:attr:`tasks.meta.OBSColumn.version` objects resulting from this
        task.  If this is identical to what's already in the database, the
        :class:`OBSColumn <tasks.meta.OBSColumn>` will not be replaced.
        '''
        return 0

    def prefix(self):
        if self.task_namespace and self.task_namespace != '__not_user_specified':
            splitted_namespace = self.task_namespace.split('.')
            # Backward compatibility to avoid rerun all the tasks
            # to me removed for the next rebuild
            if splitted_namespace[0] == 'tasks':
                splitted_namespace.pop(0)
                return '.'.join(splitted_namespace)
            else:
                return self.task_namespace
        else:
            return classpath(self)

    def output(self):
        session = current_session()

        # Return columns from database if the task is already finished
        if hasattr(self, '_colids') and self.complete():
            return OrderedDict([
                (colkey, ColumnTarget(session.query(OBSColumn).get(cid), self))
                for colkey, cid in self.colids.items()
            ])

        # Otherwise, run `columns` (slow!) to generate output
        already_in_session = [obj for obj in session]
        output = OrderedDict()

        input_ = self.input()
        for col_key, col in self.columns().items():
            if not isinstance(col, OBSColumn):
                raise RuntimeError(
                    'Values in `.columns()` must be of type OBSColumn, but '
                    '"{col}" is type {type}'.format(col=col_key, type=type(col)))
            if not col.version:
                col.version = self.version()
            col.id = '.'.join([self.prefix(), col.id or col_key])
            tags = self.tags(input_, col_key, col)
            if isinstance(tags, TagTarget):
                col.tags.append(tags)
            else:
                col.tags.extend(tags)

            output[col_key] = ColumnTarget(col, self)
        now_in_session = [obj for obj in session]
        for obj in now_in_session:
            if obj not in already_in_session:
                if obj in session:
                    session.expunge(obj)
        return output

    @property
    def colids(self):
        '''
        Return colids for the output columns, this can be cached
        '''
        if not hasattr(self, '_colids'):
            self._colids = OrderedDict([
                (colkey, ct._id) for colkey, ct in self.output().items()
            ])
        return self._colids

    def complete(self):
        '''
        Custom complete method that attempts to check if output exists, as is
        default, but in case of failure allows attempt to run dependencies (a
        missing dependency could result in exception on `output`).
        '''
        deps = self.deps()
        if deps and not all([d.complete() for d in deps]):
            return False
        else:
            # bulk check that all columns exist at proper version
            cnt = current_session().execute(
                '''
                SELECT COUNT(*)
                FROM observatory.obs_column
                WHERE id IN ('{ids}') AND version = '{version}'
                '''.format(
                    ids="', '".join(list(self.colids.values())),
                    version=self.version()
                )).fetchone()[0]
            return cnt == len(list(self.colids.values()))

    def tags(self, input_, col_key, col):
        '''
        Replace with an iterable of :class:`OBSColumn <tasks.meta.OBSColumn>`
        that should be applied to each column

        :param input_: A saved version of this class's :meth:`input <luigi.Task.input>`
        :param col_key: The key of the column this will be applied to.
        :param column: The :class:`OBSColumn <tasks.meta.OBSColumn>` these tags
                       will be applied to.
        '''
        return [] class TagsTask(Task):
    '''
    This will update-or-create :class:`OBSTag <tasks.meta.OBSTag>` objects
    int the database when run.

    The :meth:`~.TagsTask.tags` method must be overwritten.

    :meth:`~TagsTask.version` is used to control updates to the database.
    '''

    def tags(self):
        '''
        This method must be overwritten in subclasses.

        The return value must be an iterable of instances of
        :class:`OBSTag <tasks.meta.OBSTag>`.
        '''
        raise NotImplementedError('Must return iterable of OBSTags')

    def on_failure(self, ex):
        session_rollback(self, ex)
        super(TagsTask, self).on_failure(ex)

    def on_success(self):
        session_commit(self)

    def run(self):
        for _, tagtarget in self.output().items():
            tagtarget.update_or_create()

    def version(self):
        return 0

    def output(self):
        # if self.deps() and not all([d.complete() for d in self.deps()]):
        #    raise Exception('Must run prerequisites first')
        output = {}
        for tag in self.tags():
            orig_id = tag.id
            tag.id = '.'.join([classpath(self), orig_id])
            if not tag.version:
                tag.version = self.version()
            output[orig_id] = TagTarget(tag, self)
        return output class SectionTags(TagsTask):

    def version(self):
        return 5

    def tags(self):
        return [
            OBSTag(id='global',
                   name='Global',
                   type='section',
                   description=''),
            OBSTag(id='eu',
                   name='European Union',
                   type='section',
                   description=''),
            OBSTag(id='united_states',
                   name='United States',
                   type='section',
                   description=''),
            OBSTag(id='spain',
                   name='Spain',
                   type='section',
                   description=''),
            OBSTag(id='mx',
                   name='Mexico',
                   type='section',
                   description=''),
            OBSTag(id='uk',
                   name='United Kingdom',
                   type='section',
                   description=''),
            OBSTag(id='fr',
                   name='France',
                   type='section',
                   description=''),
            OBSTag(id='ca',
                   name='Canada',
                   type='section',
                   description=''),
            OBSTag(id='th',
                   name='Thailand',
                   type='section',
                   description=''),
            OBSTag(id='au',
                   name='Australia',
                   type='section',
                   description=''),
            OBSTag(id='br',
                   name='Brazil',
                   type='section',
                   description='')
        ] class SubsectionTags(TagsTask):

    def version(self):
        return 24

    def tags(self):
        return [
            OBSTag(id='housing',
                   name='Housing',
                   type='subsection',
                   description='What type of housing exists and how do people live in it?'),
            OBSTag(id='income',
                   name='Income',
                   type='subsection',
                   description='''How much people earn.'''),
            OBSTag(id='education',
                   name='Education',
                   type='subsection',
                   description='Educational attainment and enrollment.'),
            OBSTag(id='employment',
                   name='Employment',
                   type='subsection',
                   description='How people are employed.'),
            OBSTag(id='families',
                   name="Families",
                   type='subsection',
                   description='Familial arrangements of people and households.'),
            OBSTag(id='language',
                   name="Language",
                   type='subsection',
                   description='What languages people speak.'),
            OBSTag(id='age_gender',
                   name='Age and Gender',
                   type='subsection',
                   description='Population breakdowns by age and gender.'),
            OBSTag(id='nationality',
                   name='Nationality',
                   type='subsection',
                   description='''Population breakdowns by nationality and place of birth.''',),
            OBSTag(id='race_ethnicity',
                   name='Race and Ethnicity',
                   type='subsection',
                   description='''Population breakdowns by race and ethnicity. '''),
            OBSTag(id='transportation',
                   name='Transportation',
                   type='subsection',
                   description='How do people move from place to place?'),
            OBSTag(id='boundary',
                   name='Boundaries',
                   type='subsection',
                   description='Political, administrative, and census-based boundaries.'),
            OBSTag(id='religion',
                   name='Religion',
                   type='subsection',
                   description='Breakdowns of the population by religion.'),
            OBSTag(id='migration',
                   name='Migration',
                   type='subsection',
                   description='Patterns of migration.'),
            OBSTag(id='health',
                   name='Health',
                   type='subsection',
                   description='Breakdowns of the population by health.'),
            OBSTag(id='commerce_economy',
                   name='Commerce and Economy',
                   type='subsection',
                   description='Broader measures of economic and commercial activity.'),
            OBSTag(id='business_demography',
                   name='Business Demography',
                   type='subsection',
                   description='Measures of enterprise growth and decline.'),
            OBSTag(id='segments',
                   name='Population segments',
                   type='subsection',
                   description='Segmentations of the population.'),
            OBSTag(id='society',
                   name='Society',
                   type='subsection',
                   description='Measures of society quality of life'),
            OBSTag(id='energy',
                   name='Energy',
                   type='subsection',
                   description='Measures related to energy production and consumption.'),
            OBSTag(id='environmental',
                   name='Environmental',
                   type='subsection',
                   description='Attributes of the natural environment'),
            OBSTag(id='roads',
                   name='Roads',
                   type='subsection',
                   description=''),
            OBSTag(id='names',
                   name='Proper names',
                   type='subsection',
                   description='Proper names for boundaries and other features'),
            OBSTag(id='poi',
                   name='Points of Interest',
                   type='subsection',
                   description=''),
        ] class UnitTags(TagsTask):

    def version(self):
        return 11

    def tags(self):
        return [
            OBSTag(id='index',
                   name='Index',
                   type='unit',
                   description=''),
            OBSTag(id='people',
                   name='People',
                   type='unit',
                   description=''),
            OBSTag(id='households',
                   name='Households',
                   type='unit',
                   description=''),
            OBSTag(id='housing_units',
                   name='Housing Units',
                   type='unit',
                   description=''),
            OBSTag(id='money',
                   name='Money',
                   type='unit',
                   description=''),
            OBSTag(id='ratio',
                   name='Ratio',
                   type='unit',
                   description=''),
            OBSTag(id='years',
                   name='Years',
                   type='unit',
                   description=''),
            OBSTag(id='minutes',
                   name='Minutes',
                   type='unit',
                   description=''),
            OBSTag(id='days',
                   name='Days',
                   type='unit',
                   description=''),
            OBSTag(id='weeks',
                   name='Weeks',
                   type='unit',
                   description=''),
            OBSTag(id='nights',
                   name='Nights',
                   type='unit',
                   description=''),
            OBSTag(id='telephones',
                   name='Telephones',
                   type='unit',
                   description=''),
            OBSTag(id='vehicles',
                   name='Vehicles',
                   type='unit',
                   description=''),
            OBSTag(id='businesses',
                   name='Businesses',
                   type='unit',
                   description=''),
            OBSTag(id='education_level',
                   name='Level of education',
                   type='unit',
                   description=''),
            OBSTag(id='segmentation',
                   name='Segmentation',
                   type='unit',
                   description=''),
            OBSTag(id='rooms',
                   name='Rooms',
                   type='unit',
                   description=''),
            OBSTag(id='beds',
                   name='Beds',
                   type='unit',
                   description=''),
            OBSTag(id='family_unit',
                   name='Families',
                   type='unit',
                   description=''),
            OBSTag(id='hours',
                   name='Hours',
                   type='unit',
                   description=''),
            OBSTag(id='crimes',
                   name='Crimes',
                   type='unit',
                   description=''),
            OBSTag(id='tonnes',
                   name='Metric Tonnes',
                   type='unit',
                   description=''),
            OBSTag(id='kilometres',
                   name='Kilometres',
                   type='unit',
                   description=''),
            OBSTag(id='inches',
                   name='Inches',
                   type='unit',
                   description=''),
            OBSTag(id='km2',
                   name='Kilometers squared',
                   type='unit',
                   description=''),
            OBSTag(id='tax_consumption_units',
                   name='Tax Consumption Units',
                   type='unit',
                   description=' CU for the first adult in the household, '
                               ' 0.5 CU for the other persons aged 14 years or older, '
                               ' 0.3 CU for the children under 14 years. '),
        ] class LicenseTags(TagsTask):

    def version(self):
        return 3

    def tags(self):
        return [
            OBSTag(id='cc-by-4',
                   name='CC-BY 4.0',
                   type='license',
                   description=''),
            OBSTag(id='no-restrictions',
                   name='Unrestricted',
                   type='license',
                   description='You may do whatever you want with this information'),
            OBSTag(id='uk_ogl',
                   name='UK Open Government Licence (OGL)',
                   type='license',
                   description='The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive licence to use the Information subject to the conditions `here <http://www.nationalarchives.gov.uk/doc/open-government-licence/version/2/>`_.'),
            OBSTag(id='uk_ed_datashare',
                   name='Edinburgh DataShare',
                   type='license',
                   description='Conditions `here <https://datashare.is.ed.ac.uk/bitstream/handle/10283/2405/license.txt?sequence=3&isAllowed=y>`_.'),
            OBSTag(id='ine_property',
                   name='National Statistics Institute (INE) Property',
                   type='license',
                   description='http://www.ine.es/ss/Satellite?L=1&c=Page&cid=1254735849170&p=1254735849170&pagename=Ayuda%2FINELayout#'),
            OBSTag(id='zillow',
                   name='Zillow Group Data',
                   type='license',
                   description='http://www.zillow.com/research/data/. All data accessed and downloaded from this page is free for public use by consumers, media, analysts, academics etc., consistent with our published Terms of Use. Proper and clear attribution of all data to Zillow is required.'),
            OBSTag(id='eurostat_copyright',
                   name='Eurostat Copyright',
                   type='license',
                   description='''
                               Eurostat has a policy of encouraging free re-use of its data, both for
                               non-commercial and commercial purposes. All statistical
                               data, metadata, content of web pages or other dissemination
                               tools, official publications and other documents published
                               on its website, with the exceptions listed below, can be
                               reused without any payment or written licence provided that:
                               the source is indicated as Eurostat; when re-use involves
                               modifications to the data or text, this must be stated
                               clearly to the end user of the information. `Full copyright
                               notice
                               <http://ec.europa.eu/eurostat/about/our-partners/copyright>`_.'''),
            OBSTag(id='us_government_works',
                   name='U.S. Government Works',
                   type='license',
                   description='''
                               United States government creative works, including writing, images,
                               and computer code, are usually prepared by officers or employees
                               of the United States government as part of their official duties.
                               A government work is generally not subject to copyright in the United States
                               and there is generally no copyright restriction on reproduction,
                               derivative works, distribution, performance, or display of a government work.
                               Unless the work falls under an exception, anyone may, without restriction under
                               U.S. copyright laws, reproduce the work in print or digital form,
                               create derivative works, perform the work publicly, display the work,
                               distribute copies or digitally transfer the work to the public by sale
                               or other transfer of ownership, or by rental, lease, or lending.
                               `Full copyright notice
                               <https://www.usa.gov/government-works>`_.'''),
        ] DENOMINATOR = 'denominator' UNIVERSE = 'universe' class OBSColumn(Base):
    '''
    Describes the characteristics of data in a column, which can exist in
    multiple physical tables.

    These should *only* be instantiated to generate the return value of
    :meth:`.ColumnsTask.columns`.  Any other usage could have unexpected
    consequences.

    .. py:attribute:: id

       The unique identifier for this column.  Should be qualified by
       the module name of the class that created it.  Is automatically
       generated by :class:`~.tasks.ColumnsTask` if left unspecified,
       and automatically qualified by module name either way.

    .. py:attribute:: type

       The type of this column -- for example, ``Text``, ``Numeric``,
       ``Geometry``, etc.  This is used to generate a schema for any
       table using this column.

    .. py:attribute:: name

       The human-readable name of this column.  This is used in the
       catalog and API.

    .. py:attribute:: description

       The human-readable description of this column.  THis is
       used in the catalog.

    .. py:attribute:: weight

       A numeric weight for this column.  Higher weights are favored
       in certain rankings done by the API. Defaults to zero, which hides
       the column from catalog.

    .. py:attribute:: aggregate

       How this column can be aggregated.  For example,
       populations can be summed, so a population column should
       be ``sum``. Should be left blank if it is not to
       aggregate.

    .. py:attribute:: tables

       Iterable of all linked :class:`~.meta.OBSColumnTable`s, which could be
       traversed to find all tables with this column.

    .. py:attribute:: tags

       Iterable of all linked :class:`~.meta.OBSColumnTag`s, which could be
       traversed to find all tags applying to this column.

    .. py:attribute:: targets

       Dict of all related columns.  Format is ``<OBSColumn>: <reltype>``.

    .. py:attribute:: version

       A version control number, used to determine whether the column and its
       metadata should be updated.

    .. py:attribute:: extra

       Arbitrary additional information about this column stored as JSON.

    '''
    __tablename__ = 'obs_column'

    id = Column(Text, primary_key=True) # fully-qualified id like '"us.census.acs".b01001001'

    type = Column(Text, nullable=False) # postgres type, IE numeric, string, geometry, etc.
    name = Column(Text) # human-readable name to provide in bigmetadata

    description = Column(Text) # human-readable description to provide in
                                 # bigmetadata

    weight = Column(Numeric, default=1)
    aggregate = Column(Text) # what aggregate operation to use when adding
                               # these together across geoms: AVG, SUM etc.

    tables = relationship("OBSColumnTable", back_populates="column", cascade="all,delete")
    session = current_session()
    # Passing the same session object to the creator saves thousands
    # of `select 1` queries, because each use of the session was pinging
    tag_creator_with_session = functools.partial(tag_creator, session=session)
    tags = association_proxy('column_column_tags', 'tag', creator=tag_creator_with_session)

    targets = association_proxy('tgts', 'reltype', creator=coltocoltargets_creator)

    version = Column(Numeric, default=0, nullable=False)
    extra = Column(JSON)

    @property
    def sources(self):
        sources = {}
        session = current_session()
        for c2c in session.query(OBSColumnToColumn).filter_by(target_id=self.id):
            sources[c2c.source] = c2c.reltype
        return sources

    @property
    def index_type(self):
        if hasattr(self, '_index_type'):
            pass
        elif 'geom_ref' in list(self.targets.values()):
            self._index_type = 'btree'
        elif self.type.lower() == 'geometry':
            self._index_type = 'gist'
        else:
            self._index_type = None

        return self._index_type

    def children(self):
        children = [col for col, reltype in self.sources.items() if reltype == DENOMINATOR]
        children.sort(key=lambda x: natural_sort_key(x.name))
        return children

    def is_cartographic(self):
        '''
        Returns True if this column is a geometry that can be used for cartography.
        '''
        for tag in self.tags:
            if 'cartographic_boundary' in tag.id:
                return True
        return False

    def is_interpolation(self):
        '''
        Returns True if this column is a geometry that can be used for interpolation.
        '''
        for tag in self.tags:
            if 'interpolation_boundary' in tag.id:
                return True
        return False

    def is_geomref(self):
        '''
        Returns True if the column is a geomref, else Null
        '''
        return GEOM_REF in list(self.targets.values())

    def has_children(self):
        '''
        Returns True if this column has children, False otherwise.
        '''
        return len(self.children()) > 0

    def has_denominators(self):
        '''
        Returns True if this column has no denominator, False otherwise.
        '''
        return DENOMINATOR in list(self.targets.values())

    def has_catalog_image(self):
        '''
        Returns True if this column has a pre-generated image for the catalog.
        '''
        return os.path.exists(os.path.join('catalog', 'img', self.id + '.png'))

    def denominators(self):
        '''
        Return the :class:`~.meta.OBSColumn` denominator of this column.
        '''
        if not self.has_denominators():
            return []
        return [k for k, v in self.targets.items() if v == DENOMINATOR]

    def unit(self):
        '''
        Return the :class:`~.meta.OBSTag` unit of this column.
        '''
        units = [tag for tag in self.tags if tag.type == 'unit']
        if units:
            return units[0]

    def summable(self):
        '''
        Returns True if we can sum this column and calculate for arbitrary
        areas.
        '''
        return self.aggregate == 'sum'

    def catalog_lonlat(self):
        '''
        Return tuple (longitude, latitude) for the catalog for this measurement.
        '''
        return catalog_lonlat(self.id)

    def geom_timespans(self):
        '''
        Return a dict of geom columns and timespans that this measure is
        available for.
        '''
        geom_timespans = {}
        for table in self.tables:
            table = table.table
            geomref_column = table.geomref_column()
            geom_column = [t for t, rt in geomref_column.targets.items()
                           if rt == GEOM_REF][0]
            if geom_column not in geom_timespans:
                geom_timespans[geom_column] = []
            geom_timespans[geom_column].append(table.timespan)
        return geom_timespans

    def source_tags(self):
        '''
        Return source tags.
        '''
        return [tag for tag in self.tags if tag.type.lower() == 'source']

    def license_tags(self):
        '''
        Return license tags.
        '''
        return [tag for tag in self.tags if tag.type.lower() == 'license'] class OBSTag(Base):
    '''
    Tags permit arbitrary groupings of columns.

    They should only be created as part of a :meth:`~.tasks.TagsTask.tags`
    implementation.

    .. py:attribute:: id

       The unique identifier for this table.  Is always qualified by
       the module name of the :class:`~.tasks.TagsTask` that created it, and
       should never be specified manually.

    .. py:attribute:: name

       The name of this tag.  This is exposed in the API and user interfaces.

    .. py:attribute:: type

       The type of this tag.  This is used to provide more information about
       what the tag means.  Examples are ``section``, ``subsection``,
       ``license``, ``unit``, although any arbitrary type can be defined.

    .. py:attribute:: description

       Description of this tag.  This may be exposed in the API and catalog.

    .. py:attribute:: columns

       An iterable of all the :class:`~.meta.OBSColumn`s tagged with this tag.

    .. py:attribute:: version

       A version control number, used to determine whether the tag and its
       metadata should be updated.

    '''
    __tablename__ = 'obs_tag'

    id = Column(Text, primary_key=True)

    name = Column(Text, nullable=False)
    type = Column(Text, nullable=False)
    description = Column(Text)

    columns = association_proxy('tag_column_tags', 'column')

    version = Column(Numeric, default=0, nullable=False) def current_session():
    '''
    Returns the session relevant to the currently operating :class:`Task`, if
    any.  Outside the context of a :class:`Task`, this can still be used for
    manual session management.
    '''
    return _current_session.get() class SegmentTags(TagsTask):

    def tags(self):

        return [
            OBSTag(id='middle_aged_men',
                   name='Middle Aged Men (45 to 64 years old)',
                   type='segment',
                   description=''),
            OBSTag(id='families_with_young_children',
                   name='Families with young children (Under 6 years old)',
                   type='segment',
                   description='')
        ] class ColumnsDeclarations:
    def __init__(self, JSONFile):
        with open(JSONFile) as infile:
            self._columns = json.load(infile)

    def _find_column(self, colid):
        return self._columns.get(colid)

    def _is_column_filtered(self, colid, parameters):
        params = json.loads(parameters)

        column = self._find_column(colid)
        if column:
            conditions = column.get(CONDITIONS)
            if conditions and not self._check_requirements(params, conditions):
                return True

            exceptions = column.get(EXCEPTIONS)
            if exceptions and self._check_requirements(params, exceptions):
                return True

        return False

    def _check_requirements(self, parameters, requirements):
        for requirement in requirements:
            for param_id, value in parameters.items():
                if param_id in requirement:
                    param_value = requirement[param_id]
                    if isinstance(param_value, (list,)):
                        if value not in param_value:
                            break
                    else:
                        if param_value != value:
                            break
            else:
                return True

        return False

    def filter_columns(self, columns, parameters):
        return OrderedDict([[k, v] for k, v in columns.items() if not self._is_column_filtered(k, parameters)])
import os
from luigi import Parameter
from tasks.base_tasks import ColumnsTask, TagsTask
from tasks.tags import SectionTags, SubsectionTags, UnitTags, LicenseTags
from tasks.meta import DENOMINATOR, UNIVERSE, OBSColumn, OBSTag, current_session
from tasks.us.census.segments import SegmentTags
from collections import OrderedDict
from lib.columns import ColumnsDeclarations

NAMESPACE = 'tasks.us.census.acs'


class ACSTags(TagsTask):

    task_namespace = NAMESPACE

    def version(self):
        return 4

    def tags(self):
        return [
            OBSTag(id='acs',
                   name='US American Community Survey',
                   type='source',
                   description='`The United States American Community Survey <https://www.census.gov/programs-surveys/acs/>`_'),
            OBSTag(id='segments',
                   name='US Population Segments',
                   type='subsection',
                   description='Segmentation of the United States population'),
        ]


class Columns(ColumnsTask):
    year = Parameter()
    sample = Parameter()
    geography = Parameter()

    resources = {"obs_column": 1}
    task_namespace = NAMESPACE

    def requires(self):
        return {

            'sections': SectionTags(),